JavaMail: сохранение IMAPFolder.idle()
Я делаю программу, которая должна контролировать учетную запись Gmail для новых сообщений, и для того, чтобы получить их как можно скорее, я использую функцию простоя JavaMail. Вот фрагмент кода из потока, который я использую, для вызова folder.idle():
//Run method that waits for idle input. If an exception occurs, end the thread life.
public void run() {
IMAPFolder folder = null;
try {
folder = getFolder();
while(true)
{
//If connection has been lost, attempt to restore it
if (!folder.isOpen())
folder = getFolder();
//Wait until something happens in inbox
folder.idle(true);
//Notify controller of event
cont.inboxEventOccured();
}
}
catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("MailIdleWaiter thread ending.");
}
Метод getFolder() в основном открывает соединение с сервером IMAP и открывает папку "Входящие".
Это работает некоторое время, но через 10 минут он перестает получать обновления (исключение не генерируется).
Я ищу предложения, чтобы поддерживать связь. Нужен ли мне второй поток, единственная роль которого заключается в том, чтобы спать и обновлять поток холостого хода() каждые 10 минут или есть более простой/лучший способ?
Спасибо заранее.
Ответы
Ответ 1
Общей ошибкой является предположить, что команда IDLE будет постоянно обновлять публикации. Однако RFC 2177, который определяет состояния расширения IDLE:
Сервер МОЖЕТ считать клиента неактивным, если он имеет команду IDLE и если такой сервер имеет таймаут бездействия, он МОЖЕТ регистрироваться клиент неявно в конце своего периода ожидания. Потому как из этого, клиентам, использующим IDLE, рекомендуется прекратить IDLE и переиздайте его, по крайней мере, каждые 29 минут, чтобы избежать выхода из системы. Это позволяет клиенту получать немедленные обновления почтового ящика даже хотя ему нужно только "опрос" с интервалом в полчаса.
GMail, в частности, имеет гораздо меньший тайм-аут, как вы говорите, около 10 минут.
Нам просто нужно переиздать команду IDLE каждые 9 минут или около того, чтобы она работала. API-интерфейсы javax.mail
не имеют возможности установить тайм-аут для команды IDLE, поэтому вам понадобится второй поток, чтобы обойти это.
Первый подход состоял бы в том, чтобы второй поток прерывал первый, обрабатывая исключение и игнорируя его. Тем не менее, это не позволило бы чистым способом закрыть поток, поэтому я не буду рекомендовать его. Более чистый способ состоит в том, чтобы второй поток выдавал команду NOOP на сервер. Это ничего не делает, но этого достаточно, чтобы IDLE прерывался и переиздавался.
Здесь я предоставляю код для этого:
public void startListening(IMAPFolder imapFolder) {
// We need to create a new thread to keep alive the connection
Thread t = new Thread(
new KeepAliveRunnable(imapFolder), "IdleConnectionKeepAlive"
);
t.start();
while (!Thread.interrupted()) {
LOGGER.debug("Starting IDLE");
try {
imapFolder.idle();
} catch (MessagingException e) {
LOGGER.warn("Messaging exception during IDLE", e);
throw new RuntimeException(e);
}
}
// Shutdown keep alive thread
if (t.isAlive()) {
t.interrupt();
}
}
/**
* Runnable used to keep alive the connection to the IMAP server
*
* @author Juan Martín Sotuyo Dodero <[email protected]>
*/
private static class KeepAliveRunnable implements Runnable {
private static final long KEEP_ALIVE_FREQ = 300000; // 5 minutes
private IMAPFolder folder;
public KeepAliveRunnable(IMAPFolder folder) {
this.folder = folder;
}
@Override
public void run() {
while (!Thread.interrupted()) {
try {
Thread.sleep(KEEP_ALIVE_FREQ);
// Perform a NOOP just to keep alive the connection
LOGGER.debug("Performing a NOOP to keep alvie the connection");
folder.doCommand(new IMAPFolder.ProtocolCommand() {
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
p.simpleCommand("NOOP", null);
return null;
}
});
} catch (InterruptedException e) {
// Ignore, just aborting the thread...
} catch (MessagingException e) {
// Shouldn't really happen...
LOGGER.warn("Unexpected exception while keeping alive the IDLE connection", e);
}
}
}
}
Ответ 2
Предложение @user888307 - грязный хак и, в конечном счете, терпит неудачу. Существует действительно только один правильный способ сделать это.
Вызвать метод простоя (false) в выбранной в данный момент папке. Идеально Входящие, потому что они получат все сообщения.
Вызов idle (false) будет в основном зависать во время выполнения потока, поэтому лучше положить idle (false) в новый поток. Затем, как только вы получите новое электронное сообщение/уведомление, используя messageCountChange, вам нужно повторно запустить этот поток.
Это единственный верный способ достижения этого. Я написал обертку для вашей явной проблемы, поскольку я пишу программу под названием JavaPushMail. Вы можете найти более подробную информацию на моем сайте (http://www.mofirouz.com/wordpress), или вы можете захватить приложение (которое в настоящее время находится в разработке) на GitHub https://github.com/mofirouz/JavaPushMail
Ответ 3
Фактически образцы Java Mail включают пример ID IMAP IDLE, который выглядит следующим образом.
Кроме того, может быть интересно класс IdleManager.
/*
* Copyright (c) 1996-2010 Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.*;
import java.io.*;
import javax.mail.*;
import javax.mail.event.*;
import javax.activation.*;
import com.sun.mail.imap.*;
/* Monitors given mailbox for new mail */
public class monitor {
public static void main(String argv[]) {
if (argv.length != 5) {
System.out.println(
"Usage: monitor <host> <user> <password> <mbox> <freq>");
System.exit(1);
}
System.out.println("\nTesting monitor\n");
try {
Properties props = System.getProperties();
// Get a Session object
Session session = Session.getInstance(props, null);
// session.setDebug(true);
// Get a Store object
Store store = session.getStore("imap");
// Connect
store.connect(argv[0], argv[1], argv[2]);
// Open a Folder
Folder folder = store.getFolder(argv[3]);
if (folder == null || !folder.exists()) {
System.out.println("Invalid folder");
System.exit(1);
}
folder.open(Folder.READ_WRITE);
// Add messageCountListener to listen for new messages
folder.addMessageCountListener(new MessageCountAdapter() {
public void messagesAdded(MessageCountEvent ev) {
Message[] msgs = ev.getMessages();
System.out.println("Got " + msgs.length + " new messages");
// Just dump out the new messages
for (int i = 0; i < msgs.length; i++) {
try {
System.out.println("-----");
System.out.println("Message " +
msgs[i].getMessageNumber() + ":");
msgs[i].writeTo(System.out);
} catch (IOException ioex) {
ioex.printStackTrace();
} catch (MessagingException mex) {
mex.printStackTrace();
}
}
}
});
// Check mail once in "freq" MILLIseconds
int freq = Integer.parseInt(argv[4]);
boolean supportsIdle = false;
try {
if (folder instanceof IMAPFolder) {
IMAPFolder f = (IMAPFolder)folder;
f.idle();
supportsIdle = true;
}
} catch (FolderClosedException fex) {
throw fex;
} catch (MessagingException mex) {
supportsIdle = false;
}
for (;;) {
if (supportsIdle && folder instanceof IMAPFolder) {
IMAPFolder f = (IMAPFolder)folder;
f.idle();
System.out.println("IDLE done");
} else {
Thread.sleep(freq); // sleep for freq milliseconds
// This is to force the IMAP server to send us
// EXISTS notifications.
folder.getMessageCount();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Ответ 4
проверка количества сообщений каждые 5 минут работает для меня:
new Thread()
{
@Override
public void run()
{
startTimer();
}
private void startTimer()
{
int seconds = 0;
while (true)
{
try
{
Thread.sleep(300000);
int c = folder.getMessageCount();
}
catch (InterruptedException ex)
{
}
catch (MessagingException me)
{
}
}
}
}.start();