/**
*
*/
package fr.cedrik.email.fs;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.MailParseException;
import fr.cedrik.email.EMailProperties;
import fr.cedrik.email.FoldersList;
import fr.cedrik.email.MessagesMetaData;
import fr.cedrik.email.spi.Message;
import fr.cedrik.email.spi.PropertiesFileSupplier;
import fr.cedrik.email.spi.Session;
import fr.cedrik.email.spi.SessionSupplier;
import fr.cedrik.inotes.Folder;
import fr.cedrik.util.DateUtils;
import fr.cedrik.util.IteratorChain;
/**
* @author Cédrik LIME
*/
public abstract class BaseFsExport implements fr.cedrik.email.MainRunner.Main {
protected static final String ISO8601_DATE_SEMITIME = "yyyy-MM-dd'T'HH:mm";//$NON-NLS-1$
protected static final String PREF_LAST_EXPORT_DATE = "lastExportDate";//$NON-NLS-1$
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected EMailProperties email;
protected Session session;
protected Date oldestMessageToFetch, newestMessageToFetch;
protected BaseFileWriter writer;
public BaseFsExport() throws IOException {
}
protected void run(String[] args, String extension) throws IOException {
if (args.length == 0) {
help();
System.exit(-1);
}
if (! prepareDestinationObjects(args[0], extension)) {
return;
}
email = PropertiesFileSupplier.Util.get();
// login
session = SessionSupplier.Util.get(email);
if (! session.login(email.getUserName(), email.getUserPassword())) {
logger.error("Can not login user {}!", email.getUserName());
return;
}
try {
// export folders hierarchy
FoldersList folders = session.getFolders();
Folder folder = folders.getFolderById(email.getCurrentFolderId());
if (folder == null) {
throw new IllegalArgumentException("Can not find folder \"" + email.getCurrentFolderId() + "\". Did you input the folder name instead of its id?");
}
export(folder, args);
} finally {
session.logout();
}
}
protected abstract void help();
protected void export(Folder folder, String[] args) throws IOException {
boolean deleteExportedMessages = false;
session.setCurrentFolder(folder);
this.oldestMessageToFetch = null; // reset
if (args.length > 1) {
try {
this.oldestMessageToFetch = new SimpleDateFormat(ISO8601_DATE_SEMITIME).parse(args[1]);
} catch (ParseException ignore) {
logger.warn("Bad date format: {} Please use {}", args[1], ISO8601_DATE_SEMITIME);
}
if (args.length > 2) {
try {
this.newestMessageToFetch = new SimpleDateFormat(ISO8601_DATE_SEMITIME).parse(args[2]);
if (oldestMessageToFetch.after(newestMessageToFetch)) {
logger.error("End date must be _after_ start date! Exiting…");
return;
}
} catch (ParseException ignore) {
logger.warn("Bad date format: {} Please use {}", args[2], ISO8601_DATE_SEMITIME);
}
}
if (args.length > 3) {
if (this.newestMessageToFetch != null && "--delete".equals(args[3])) {
logger.warn("Flagging exported messages for deletion");
deleteExportedMessages = true;
}
}
} else if (shouldLoadOldestMessageToFetchFromPreferences()) {
// set oldestMessageToFetch if file exists, and there is a Preference
try {
Preferences prefs = getUserNode(false);
if (prefs != null) {
long lastExportDate = prefs.getLong(PREF_LAST_EXPORT_DATE, -1);
if (lastExportDate > 1) {// Don't incremental-export if last exported message date is null!
this.oldestMessageToFetch = new Date(lastExportDate);
this.newestMessageToFetch = null;
}
}
} catch (BackingStoreException ignore) {
logger.warn("Can not load last export date:", ignore);
}
}
if (this.oldestMessageToFetch != null) {
String msg = folder.getName() + ": incremental export from " + DateUtils.ISO8601_DATE_TIME_FORMAT.format(this.oldestMessageToFetch);
if (newestMessageToFetch != null) {
msg += " to " + DateUtils.ISO8601_DATE_TIME_FORMAT.format(this.newestMessageToFetch);
}
if (deleteExportedMessages) {
msg += " with source deletion";
}
logger.info(msg);
} else {
logger.info(folder.getName() + ": full export");
}
// messages and meeting notices meta-data
MessagesMetaData<? extends Message> messages = session.getMessagesMetaData(oldestMessageToFetch, newestMessageToFetch);
if (folder.isInbox() || folder.isAllMails()) {
checkQuota(messages);
}
if (! messages.entries.isEmpty()) {
final boolean append = writer.exists() && (oldestMessageToFetch != null);
Date lastExportedMessageDate = this.export(messages, append, deleteExportedMessages);
if (lastExportedMessageDate != null && lastExportedMessageDate.getTime() > 0) {
if (oldestMessageToFetch != null) {
assert lastExportedMessageDate.equals(oldestMessageToFetch) || lastExportedMessageDate.after(oldestMessageToFetch);
}
if (newestMessageToFetch != null) {
assert lastExportedMessageDate.before(newestMessageToFetch) || lastExportedMessageDate.equals(newestMessageToFetch);
}
if (newestMessageToFetch == null) {
// incremental export: set Preference to oldestMessageToFetch
setPreferenceToOldestMessageToFetch(lastExportedMessageDate);
}
}
}
}
/**
* @return last exported message date (can be {@code null})
*/
protected final Date export(MessagesMetaData<? extends Message> messages, boolean append, boolean deleteExportedMessages) throws IOException {
Date lastExportedMessageDate = null;
try {
writer.openFolder(append);
// write messages
List<Message> writtenMessages = new ArrayList<Message>();
for (Message message : messages.entries) {
IteratorChain<String> mime;
try {
mime = session.getMessageMIME(message);
} catch (MailParseException mpe) {
mime = null;
}
if (mime == null || ! mime.hasNext()) {
logger.error("Empty MIME message! ({})", message);
IOUtils.closeQuietly(mime);
break;
}
try {
writer.openFile(message, append);
writer.write(message, mime);
writtenMessages.add(message);
} finally {
IOUtils.closeQuietly(mime);
writer.closeFile(message);
}
if (message.getDate().getTime() > 0) {
lastExportedMessageDate = message.getDate();
}
}
writer.flush();
if (deleteExportedMessages) {
session.deleteMessage(writtenMessages);
}
} finally {
writer.closeFolder();
}
return lastExportedMessageDate;
}
/**
* Create Java objects and store them in fields. Does not physically create files.
*/
protected abstract boolean prepareDestinationObjects(String baseName, String extension);
/**
* Usually, check if outFile exists
*/
protected abstract boolean shouldLoadOldestMessageToFetchFromPreferences();
protected void checkQuota(MessagesMetaData<?> messages) {
if (messages.ignorequota == 0 && messages.sizelimit > 0) {
String quotaDetails = "dbsize: " + messages.dbsize
+ " currentusage: " + messages.currentusage
+ " warning: " + messages.warning
+ " sizelimit: " + messages.sizelimit
+ " ignorequota: " + messages.ignorequota;
if (messages.dbsize >= messages.sizelimit || messages.currentusage >= messages.sizelimit) {
logger.warn("WARNING WARNING: you have exceeded your quota! " + quotaDetails);
} else if (messages.dbsize > messages.warning || messages.currentusage > messages.warning) {
logger.info("WARNING: you are nearing your quota! " + quotaDetails);
}
}
}
protected void setPreferenceToOldestMessageToFetch(Date lastExportDate) {
try {
Preferences prefs = getUserNode(true);
if (lastExportDate != null && lastExportDate.getTime() > 0) {
logger.debug("Recording last export date: {} for: {}", lastExportDate, prefs);
prefs.putLong(PREF_LAST_EXPORT_DATE, lastExportDate.getTime()+1);// +1: don't re-export last exported message next time...
} else {
logger.debug("Resetting (null) last export date for: {}", prefs);
prefs.remove(PREF_LAST_EXPORT_DATE);
}
prefs.flush();
} catch (BackingStoreException ignore) {
logger.warn("Can not store last export date: " + DateUtils.ISO8601_DATE_TIME_FORMAT.format(lastExportDate), ignore);
}
}
protected Preferences getUserNode(boolean create) throws BackingStoreException {
Preferences prefs = Preferences.userNodeForPackage(this.getClass());
String key = this.getClass().getSimpleName() + '/'
+ email.getUserName().replace('/', '\\') + '@'
+ (email.getServerAddress().replace('/', '\\')) + '/'
+ (email.getCurrentFolderId().replace('/', '\\'));
if (create || prefs.nodeExists(key)) {
return prefs.node(key);
} else {
return null;
}
}
}