JavaでBiff

Javaでかんたんタスクトレイ常駐アプリで、Javaでの常駐アプリ作成について書きました。
んで、何を作ったかっていうとBiffなんです。
Thunderbirdを使っていて、タスクトレイに収まんないのが嫌で作ってみました。

動作イメージ

タスクトレイに常駐します。

メニュー

こんなかんじでメニューがでます。

実際にメール見る時の為に、メーラーを起動できるようにしています。

メール着信時

こんなかんじで差出人と件名が表示されます。

ソース

各種設定をGUIでなく、プロパティファイルにしたので
ソースもすごく少なくなりました。


まあ、自分用のツールなら十分ではないでしょうか。

MyBiff.java

メインのクラスです。

package biff;

import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.imageio.ImageIO;

/** 僕のBiff */
public class MyBiff {

    /***/
    private static final long SEC = 1000;
    /***/
    private static final long MIN = 60 * SEC;
    /** プロパティファイル名 */
    private static final String PROP_FILE_NAME = "mybiff.properties";
    /** トレイアイコン */
    private final TrayIcon icon;
    
    /**
     * コンストラクタ
     * @param props プロパティ
     * @throws IOException
     * @throws AWTException
     */
    public MyBiff(Properties props) throws IOException, AWTException {

        // タスクトレイアイコン
        Image image = ImageIO
                .read(getClass().getResourceAsStream("mybiff.png"));
        icon = new TrayIcon(image);

        // ポップアップメニュー
        PopupMenu menu = new PopupMenu();
        // メーラー起動メニュー
        MenuItem mailerItem = new MenuItem("Mailer");
        final String mailer = props.getProperty("mailer");
        mailerItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    Runtime.getRuntime().exec(mailer);
                } catch (Exception ex) {
                    dispException(ex);
                }
            }
        });
        menu.add(mailerItem);
        // 今すぐチェック
        final TimerTask task = new MailCheckingTask(props, this);
        MenuItem checkNowItem = new MenuItem("Check Now");
        checkNowItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                task.run();
            }
        });
        menu.add(checkNowItem);
        // 終了メニュー
        MenuItem exitItem = new MenuItem("Exit");
        exitItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        menu.add(exitItem);

        icon.setPopupMenu(menu);
        // タスクトレイに格納
        SystemTray.getSystemTray().add(icon);

        // タスクのスケジューリング
        long interval = Long.parseLong(props.getProperty("interval")) * MIN;
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(task, 0, interval);
    }
    
    /**
     * メッセージ到着時のアクション
     * @param msgs メッセージ概要
     */
    void onMessageArrival(List<MailDigest> digests) {
        icon.displayMessage("新着", editMsg(digests),
                MessageType.INFO);
    }

    /**
     * 例外発生時のアクション
     * @param e 例外
     */
    void onException(Exception e) {
        dispException(e);
    }
    
    /**
     * メッセージを編集する
     * @param list
     * @return
     */
    private String editMsg(List<MailDigest> digests) {
        StringBuilder sb = new StringBuilder();
        for (MailDigest digest : digests) {
            sb.append(digest);
            sb.append("\r\n");
        }
        return sb.toString();
    }
    
    /**
     * 例外を表示する。
     * @param e 例外
     */
    private void dispException(Exception e) {
        Writer w = new StringWriter();
        PrintWriter pw = new PrintWriter(w);
        e.printStackTrace(w);
        String msg = pw.toString();
        icon.displayMessage("エラー", msg, MessageType.ERROR);
    }
    
    public static void main(String[] args) throws Exception {
        File propFile = new File(PROP_FILE_NAME);
        Properties props = new Properties();
        InputStream is = null;
        try {
            is = new FileInputStream(propFile);
            props.load(is);
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch(IOException e) {
            }
        }
        new MyBiff(props);
    }
}
MailCheckingTask.java

一定間隔で起動されるタスクのクラスです。
java.util.TimerTaskのサブクラスになります。


起動されたらメールをチェックします。
一度表示したメールは表示しないようにしています。

JavaMailを使いました。

package biff;

import java.io.UnsupportedEncodingException;
import java.util.*;
import javax.mail.*;

/** メールチェックタスク */
class MailCheckingTask extends TimerTask {

    /** ホスト名 */
    private final String host;
    /** ユーザ */
    private final String user;
    /** パスワード */
    private final String password;
    /** コールバック */
    private final MyBiff callback;
    /** キャッシュ(一度読んだメールを記憶) */
    private final Set<MailDigest> cache = new HashSet<MailDigest>();

    /** コンストラクタ */
    public MailCheckingTask(Properties props, MyBiff myBiff) {
        this(props.getProperty("host"), props.getProperty("user"), props
                .getProperty("password"), myBiff);
    }

    /** コンストラクタ */
    public MailCheckingTask(String host, String user, String pass, MyBiff myBiff) {
        this.host = host;
        this.user = user;
        this.password = pass;
        this.callback = myBiff;
    }

    /**
     * タイマータスクの実行
     */
    @Override
    public void run() {
        try {
            List<MailDigest> result = getMail();
            if (result.isEmpty()) {
                return;
            }
            callback.onMessageArrival(result);
        } catch (Exception e) {
            callback.onException(e);
        }
    }
    
    /** メール取得 */
    private List<MailDigest> getMail() throws MessagingException,
            UnsupportedEncodingException {

        Folder folder = null;
        Store store = null;
        try {
            // 接続
            Session session = Session.getDefaultInstance(
                    System.getProperties(), null);
            store = session.getStore("pop3");
            store.connect(host, -1, user, password);

            // フォルダを開く
            folder = store.getFolder("INBOX");
            folder.open(Folder.READ_ONLY);

            // メッセージを取得する
            Message[] messages = folder.getMessages();
            List<MailDigest> result = new ArrayList<MailDigest>();
            for (Message m : messages) {
                MailDigest digest = new MailDigest(m);
                if (!cache.contains(digest)) {
                    result.add(digest);
                    cache.add(digest);
                }
            }
            return result;
        } finally {
            if (folder != null && folder.isOpen()) {
                folder.close(false);
            }
            if (store != null && store.isConnected()) {
                store.close();
            }
        }
    }
}
MailDigest.java
package biff;

import java.io.UnsupportedEncodingException;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeUtility;

/**
 * メール概要
 */
class MailDigest {

    /** メッセージID */
    public final String messageId;
    
    /** 題目 */
    public final String subject;

    /** 差出人 */
    public final String from;

    /**
     * コンストラクタ
     * @param message メッセージ
     * @throws UnsupportedEncodingException
     * @throws MessagingException
     */
    public MailDigest(Message message) throws UnsupportedEncodingException,
            MessagingException {
        messageId = message.getHeader("Message-ID")[0];
        subject = message.getSubject();
        from = MimeUtility.decodeText(message.getFrom()[0].toString());
    }
    
    @Override
    public String toString() {
        return from + subject; 
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((messageId == null) ? 0 : messageId.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MailDigest other = (MailDigest) obj;
        if (messageId == null) {
            if (other.messageId != null)
                return false;
        } else if (!messageId.equals(other.messageId))
            return false;
        return true;
    }
}
mybiff.properties
mailer=C:/Program Files/Mozilla Thunderbird/thunderbird.exe
interval=10
host=your.pop3.host.name
user=youraccount
password=yourpassword