org.gcaldaemon.core.mailterm.MailTerminal.java Source code

Java tutorial

Introduction

Here is the source code for org.gcaldaemon.core.mailterm.MailTerminal.java

Source

//
// GCALDaemon is an OS-independent Java program that offers two-way
// synchronization between Google Calendar and various iCalalendar (RFC 2445)
// compatible calendar applications (Sunbird, Rainlendar, iCal, Lightning, etc).
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
// 
// Project home:
// http://gcaldaemon.sourceforge.net
//
package org.gcaldaemon.core.mailterm;

import java.io.File;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.Set;
import java.util.SortedMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.gcaldaemon.core.Configurator;
import org.gcaldaemon.core.FilterMask;
import org.gcaldaemon.core.GmailEntry;
import org.gcaldaemon.core.GmailMessage;
import org.gcaldaemon.core.GmailPool;
import org.gcaldaemon.core.StringUtils;
import org.gcaldaemon.logger.QuickWriter;

import com.google.gdata.data.HtmlTextConstruct;

/**
 * Gmail-based command line interface.
 * 
 * Created: Jan 03, 2007 12:50:56 PM
 * 
 * @author Andras Berkes
 */
public final class MailTerminal extends Thread {

    // --- CONSTANTS ---

    private static final String QUIT_COMMAND = "quit";

    private static final byte COMMAND_NOT_FOUND = 0;
    private static final byte COMMAND_EXECUTED = 1;
    private static final byte QUIT_REQUESTED = 2;

    private static final long FAST_POLLING_TIMEOUT = 60000L;
    private static final long SCRIPT_TIMEOUT = 30000L;

    // --- LOGGER ---

    private static final Log log = LogFactory.getLog(MailTerminal.class);

    // --- VARIABLES ---

    private final Configurator configurator;
    private final long pollingTimeout;
    private final String username;
    private final String password;
    private final String subject;
    private final String encoding;
    private final File scriptDir;

    private final FilterMask[] addresses;

    // --- CONSTRUCTOR ---

    public MailTerminal(ThreadGroup mainGroup, Configurator configurator) throws Exception {
        super(mainGroup, "Mail terminal");
        this.configurator = configurator;

        // Get inbox polling time
        long timeout = configurator.getConfigProperty(Configurator.MAILTERM_POLLING_GOOGLE, 10000L);
        if (timeout < 60000L) {
            log.warn("The fastest Gmail inbox polling period is '1 min'!");
            timeout = 60000L;
        }
        pollingTimeout = timeout;

        // Get Gmail user
        username = configurator.getConfigProperty(Configurator.MAILTERM_GOOGLE_USERNAME, null);
        if (username == null) {
            throw new NullPointerException("Missing username (" + Configurator.MAILTERM_GOOGLE_USERNAME + ")!");
        }

        // Get Gmail password
        password = configurator.getPasswordProperty(Configurator.MAILTERM_GOOGLE_PASSWORD);

        // Get subject of the command mails
        subject = configurator.getPasswordProperty(Configurator.MAILTERM_MAIL_SUBJECT);

        // Get script directory
        String path = configurator.getConfigProperty(Configurator.MAILTERM_DIR_PATH, "/scripts");
        scriptDir = new File(path);
        if (!scriptDir.isDirectory()) {
            scriptDir.mkdirs();
            if (!scriptDir.isDirectory()) {
                throw new Exception("Unable to read script directory (" + path + ")! Permission denied!");
            }
        }

        // Get native console encoding
        String consoleEncoding = configurator.getConfigProperty(Configurator.MAILTERM_CONSOLE_ENCODING,
                StringUtils.US_ASCII);
        try {
            StringUtils.US_ASCII.getBytes(consoleEncoding);
        } catch (Exception unsupportedEncoding) {

            // Dump supported encodings
            SortedMap map = Charset.availableCharsets();
            if (map != null) {
                Set set = map.keySet();
                if (set != null) {
                    String[] array = new String[set.size()];
                    set.toArray(array);
                    QuickWriter writer = new QuickWriter();
                    writer.write("Invalid charset (");
                    writer.write(consoleEncoding);
                    writer.write(")! Supported console encodings:\r\n");
                    for (int i = 0; i < array.length; i++) {
                        writer.write(array[i]);
                        if (i < array.length - 1) {
                            writer.write(", ");
                        }
                        if (i % 6 == 5) {
                            writer.write("\r\n");
                        }
                    }
                    log.warn(writer.toString().trim());
                }
            }
            consoleEncoding = StringUtils.US_ASCII;
        }
        encoding = consoleEncoding;

        // Get acceptable e-mail addresses
        addresses = configurator.getFilterProperty(Configurator.MAILTERM_ALLOWED_ADDRESSES, true);

        // Start listener
        log.info("Mailterm service started successfully.");
        start();
    }

    // --- DIRECTORY LISTENER LOOP ---

    public final void run() {
        try {
            sleep(5000L);
        } catch (InterruptedException interrupt) {
            log.info("Mailterm service stopped.");
            return;
        }
        for (;;) {
            try {

                // Borrow pooled Gmail connection
                GmailPool pool = configurator.getGmailPool();
                byte responseType = COMMAND_NOT_FOUND;
                GmailEntry entry = null;
                try {
                    entry = pool.borrow(username, password);

                    // Receive mails
                    responseType = receiveMails(entry);
                } finally {

                    // Recycle pooled connection
                    pool.recycle(entry);
                }

                // Shutdown mailterm
                if (responseType == QUIT_REQUESTED) {
                    throw new InterruptedException();
                }

                // Wait
                if (responseType == COMMAND_NOT_FOUND) {
                    sleep(pollingTimeout);
                } else {
                    sleep(FAST_POLLING_TIMEOUT);
                }

            } catch (InterruptedException interrupt) {
                log.info("Mailterm service stopped.");
                return;
            } catch (Exception poolException) {
                log.warn("Unexpected mailterm error!", poolException);
                log.debug("Please verify your username/password and IMAP settings!");
                try {
                    sleep(pollingTimeout);
                } catch (InterruptedException interrupt) {
                    log.info("Mailterm service stopped.");
                    return;
                }
            }
        }
    }

    // --- MAIL RECEIVER ---

    private final byte receiveMails(GmailEntry client) throws Exception {

        // Find new mails
        log.debug("Searching commands in mailbox...");
        GmailMessage[] unreadMails = client.receive(subject);
        if (unreadMails == null || unreadMails.length == 0) {
            log.debug("Mailbox is empty or subject not found.");
            return COMMAND_NOT_FOUND;
        }

        // Read mails
        byte responseType = COMMAND_NOT_FOUND;
        GmailMessage message;
        for (int i = 0; i < unreadMails.length; i++) {
            message = unreadMails[i];

            // Get reply address
            String replyAddress = message.from;

            // Check access by reply address
            if (addresses != null) {
                if (!isAddressMatch(replyAddress)) {
                    log.warn("Request refused, forbidden e-mail address (" + replyAddress + ")!");
                    continue;
                }
            }

            // Get command
            String command = message.memo;
            if (command == null) {
                log.debug("Missing command body!");
                continue;
            }
            HtmlTextConstruct html = new HtmlTextConstruct(command);
            command = html.getPlainText();
            command = command.replace('\r', ' ').replace('\n', ' ').trim();
            if (command.length() == 0) {
                log.debug("Missing command!");
                continue;
            }
            log.debug("Executing command from " + replyAddress + " (" + command + ")...");
            String reply;
            if (command.equals(QUIT_COMMAND)) {

                // Shutdown requested
                reply = "Mailterm service terminated. Bye!";
                responseType = QUIT_REQUESTED;
            } else {

                // Parse command
                String[] args = parseLine(command);

                // Execute script
                ScriptRunner runner = new ScriptRunner(scriptDir, encoding, args);
                runner.join(SCRIPT_TIMEOUT);
                reply = runner.getScriptOutput();
                if (responseType != QUIT_REQUESTED) {
                    responseType = COMMAND_EXECUTED;
                }
            }

            // Send reply
            log.debug("Command output:\r\n" + reply);
            client.send(replyAddress, null, null, "Re:" + subject, "<pre>" + reply.trim() + "</pre>", true);

            // Wait
            Thread.sleep(500);
        }
        return responseType;
    }

    private final boolean isAddressMatch(String string) {
        for (int i = 0; i < addresses.length; i++) {
            if (addresses[i].match(string)) {
                return true;
            }
        }
        return false;
    }

    // --- COMMAND LINE PARSER ---

    /**
     * Splitting a string into a command-array.
     * 
     * <BR>
     * <BR>
     * word1 word2 word3 -> "word1", "word2", "word3" <BR>
     * word1 word2="word3 'abc' def" -> "word1", "word2", "word3 'abc' def" <BR>
     * 'wo"rd1'="word2" word3 -> "wo\"rd1", "word2", "word3" <BR>
     * etc.
     * 
     * @param cmdLine
     * @return String[]
     */
    private static final String[] parseLine(String cmdLine) {
        char delimiter = ' ';
        boolean inToken = false;
        QuickWriter writer = new QuickWriter(100);
        LinkedList tokens = new LinkedList();
        for (int i = 0; i < cmdLine.length(); i++) {
            char c = cmdLine.charAt(i);
            if (inToken) {
                if (c == delimiter || (delimiter == ' ' && c == '=')) {
                    tokens.add(writer.toString());
                    writer.flush();
                    if (c == '-') {
                        writer.write(c);
                    }
                    inToken = false;
                    continue;
                }
                writer.write(c);
            } else {
                if (c == '\'') {
                    delimiter = '\'';
                    inToken = true;
                } else {
                    if (c == '"') {
                        delimiter = '"';
                        inToken = true;
                    } else if (c == ' ') {

                        // Skip
                    } else {
                        delimiter = ' ';
                        writer.write(c);
                        inToken = true;
                    }
                }
            }
            if (i == cmdLine.length() - 1 && writer.length() != 0) {
                tokens.add(writer.toString());
            }
        }
        String[] array = new String[tokens.size()];
        tokens.toArray(array);
        return array;
    }

}