package anywheresoftware.b4a.net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.net.pop3.POP3Client;
import org.apache.commons.net.pop3.POP3MessageInfo;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Permissions;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.objects.collections.Map;
import anywheresoftware.b4a.objects.streams.File.TextReaderWrapper;
/**
* POP3 object allows you to connect to mail servers and read the mail messages.
*This object returns the raw string of each message, including the headers. Parsing the raw string is currently out of the scope of this library.
*The connection is established when it is first required.
*ListCompleted event passes a parameter named Messages. This is a map with the messages IDs as keys and the messages sizes as values.
*DownloadCompleted event passes the message raw string in the Message parameter.
*Example:<code>
*Sub Process_Globals
* Dim POP As POP3
*End Sub
*Sub Globals
*
*End Sub
*
*Sub Activity_Create(FirstTime As Boolean)
* If FirstTime Then
* POP.Initialize("pop.gmail.com", 995, "
[email protected]", "mypassword", "pop")
* POP.UseSSL = True 'Gmail requires SSL.
* End If
* POP.ListMessages
*End Sub
*
*Sub POP_ListCompleted (Success As Boolean, Messages As Map)
* Log("List: " & Success)
* If Success Then
* For i = 0 To Messages.Size - 1
* Pop.DownloadMessage(Messages.GetKeyAt(i), True) 'Download all messages and delete them
* Next
* Else
* Log(LastException.Message)
* End If
* POP.Close 'The connection will be closed after all messages are downloaded
*End Sub
*Sub POP_DownloadCompleted (Success As Boolean, MessageId As Int, Message As String)
* Log("Download: " & Success & ", " & MessageId)
* If Success Then
* Log(Message)
* Log(Message.Length)
* Log(MessageId)
* Else
* Log(LastException.Message)
* End If
*End Sub</code>
*/
@Events(values= {"ListCompleted (Success As Boolean, Messages As Map)",
"DownloadCompleted (Success As Boolean, MessageId As Int, Message As String)"})
@Permissions(values = {"android.permission.INTERNET"})
@ShortName("POP3")
public class POPWrapper {
private String user, password, server;
private int port;
private static int taskId;
private String eventName;
private boolean useSSL;
private POP3Client client;
private volatile int numberOfTasks;
private ReentrantLock lock = new ReentrantLock(true);
/**
* Initializes the object.
*Server - Server address. Host name or Ip.
*Port - Mail server port.
*Username - Account user name.
*Password - Account password.
*EventName - The name of the sub that will handle the MessageSent event.
*/
public void Initialize(String Server, int Port, String Username, String Password, String EventName) {
this.user = Username;
this.password = Password;
this.server = Server;
this.port = Port;
this.eventName = EventName.toLowerCase(BA.cul);
numberOfTasks = 0;
useSSL = false;
client = new POP3Client();
}
/**
* Gets or sets whether the connection should be done with SSL sockets.
*/
public void setUseSSL(boolean b) {
useSSL = b;
}
public boolean getUseSSL() {
return useSSL;
}
private void connectIfNeeded(final BA ba) throws Exception {
if (client == null) {
throw new RuntimeException("POP3 should first be initialized.");
}
if (client.isConnected() == false) {
if (useSSL)
client.setSSL();
client.connect(server, port);
if (client.login(user, password) == false)
throw new RuntimeException("Error during login: " + client.getReplyString());
}
}
/**
* Calls the server and when data is ready raises the ListCompleted event.
*See the example described above.
*/
public void ListMessages(final BA ba) {
final int myTask = taskId++;
numberOfTasks++;
Runnable r = new Runnable() {
@Override
public void run() {
Map m = new Map(); m.Initialize();
lock.lock();
try {
try {
connectIfNeeded(ba);
POP3MessageInfo[] msgs = client.listMessages();
if (msgs == null)
throw new RuntimeException("Error listing messages: " + client.getReplyString());
for (POP3MessageInfo msg : msgs) {
m.Put(msg.number, msg.size);
}
ba.raiseEventFromDifferentThread(null, POPWrapper.this, myTask, eventName + "_listcompleted", false, new Object[] {true, m});
} catch (Exception e) {
ba.setLastException(e);
ba.raiseEventFromDifferentThread(null, POPWrapper.this, myTask, eventName + "_listcompleted", false, new Object[] {false, m});
try {
CloseNow();
} catch (IOException e1) {
e1.printStackTrace();
}
}
} finally {
endOfTask();
}
}
};
BA.submitRunnable(r, POPWrapper.this, myTask);
}
/**
* Calls the server and downloads a message. When the message is ready the DownloadedCompleted event is raised.
*MessageId - The message id which was previously retrieved by calling ListMessages.
*Delete - Whether to delete the message after it is downloaded. Note that the message will only be deleted after the connection is closed.
*/
public void DownloadMessage(final BA ba,final int MessageId,final boolean Delete) {
final int myTask = taskId++;
numberOfTasks++;
Runnable r = new Runnable() {
@Override
public void run() {
lock.lock();
try {
try {
connectIfNeeded(ba);
Reader reader;
if ((reader = client.retrieveMessage(MessageId)) == null)
throw new RuntimeException("Error retrieving message: " + client.getReplyString());
BufferedReader br = (BufferedReader)reader;
TextReaderWrapper t = new TextReaderWrapper();
t.setObject(br);
String msg = t.ReadAll();
if (Delete) {
if (client.deleteMessage(MessageId) == false) {
throw new RuntimeException("Error deleting message: " + client.getReplyString());
}
}
ba.raiseEventFromDifferentThread(null, POPWrapper.this, myTask, eventName + "_downloadcompleted", false, new Object[] {true, MessageId, msg});
} catch (Exception e) {
e.printStackTrace();
ba.setLastException(e);
ba.raiseEventFromDifferentThread(null, POPWrapper.this, myTask, eventName + "_downloadcompleted", false, new Object[] {false, MessageId, ""});
try {
CloseNow();
} catch (IOException e1) {
e1.printStackTrace();
}
}
} finally {
endOfTask();
}
}
};
BA.submitRunnable(r, POPWrapper.this, myTask);
}
private void endOfTask() {
lock.unlock();
numberOfTasks--;
if (client != null && numberOfTasks == 0) {
synchronized (client) {
client.notifyAll();
}
}
}
/**
* Closes the connection after all submitted tasks finish. Note that this method do not block.
*/
public void Close() {
final int myTask = taskId++;
if (client == null) {
return;
}
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (client) {
while (numberOfTasks > 0 && client != null) {
try {
client.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
CloseNow();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
BA.submitRunnable(runnable, this, myTask);
}
/**
* Closes the connection immediately without waiting for current tasks to finish.
*/
public synchronized void CloseNow() throws IOException {
if (client == null)
return;
if (client.isConnected()) {
client.logout();
client.disconnect();
}
synchronized (client) {
client.notifyAll();
}
client = null;
}
}