22 May 2014

C# - Asynchronious methods with a callback

I thought this one up as a nice alternative to custom events: a method that does it's work asynchroniously and that invokes a callback method when it's done.

The method

public void GetMailBoxes(Action<Exception, List<MailBox>> method) {
  BackgroundWorker worker = new BackgroundWorker();
  worker.DoWork += (object sender, DoWorkEventArgs e) => {
    e.Result = Imap.Instance.GetMailBoxes();
  };
  worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => {
    method(e.Error, mMailBoxes);
  };
  worker.RunWorkerAsync();
}

Calling the method

account.GetMailBoxes((Exception ex, List mailBoxes) => {
  if (ex != null) {
    Program.Handle(ex);
  }
  else {
    lstMailBoxes.DataSource = mailBoxes;
  }
});

05 May 2014

IMAP - Retrieving data from a mail box

IMAP is a protocol for a client to manage a mail box on a server. The following .net code connects to a mail server through IMAP and retrieves data from it:

Connect

This opens a secured TCP connection to the server.
StringBuilder result = new StringBuilder();
TcpClient connection = new TcpClient("imap.telenet.be", 993);
SslStream stream = new SslStream(connection.GetStream(), false);
stream.AuthenticateAsClient("imap.telenet.be");
StreamWriter writer = new StreamWriter(stream);
StreamReader reader = new StreamReader(stream);
result.AppendLine(reader.ReadLine());

Login

This logs you in. Always flush the command to actualy send it. All commands are sent in this fashion. Notice how each command starts with a different tag to identify it. These are usually generated with an auto-increment.
writer.WriteLine(@"e001 LOGIN myemail@telenet.be mypassword");
writer.Flush();
CheckResponseOk("Login");

CheckResponseOk

This function retrieves the response on a command and checks whether it is possitive. This is for commands that don't return data, like connect and login. These return "OK" if they succeed.
private void CheckResponseOk(string context) {
  string[] response = GetResponse();
  if (!response.Last().Contains("OK"))
    throw new ApplicationException(context + " failed:\n" + string.Join("\n", response));
}

GetResponse

This function retrieves the response on a command. A response can be multiple lines. The last line always starts with the tag that was sent in the command.
private string[] GetResponse(int logLines = -1) {
  List result = new List();
  string tag = GetTag(false);
  string line = mReader.ReadLine();
  int loggedLines = 0;

  while (!line.StartsWith(tag)) {
    result.Add(line);
    if (logLines < 0 || loggedLines < logLines) {
      mLogWriter.WriteLine(line);
      loggedLines++;
    }
    line = mReader.ReadLine();
  }

  result.Add(line);
  if (logLines < 0 || loggedLines < logLines) {
    mLogWriter.WriteLine(line);
    loggedLines++;
  }
  return result.ToArray();
}

Other commands

  • e002 SELECT INBOX selects the INBOX mail box.
  • e003 SEARCH ALL retrieves all of the mail ID's. It's also possible to pass a query.
  • e004 FETCH 1:10 (RFC822.SIZE ENVELOPE FLAGS) retrieves the size, envelope and flags of mail ID 1 through 10.
  • e005 FETCH 1 BODY[TEXT] retrieves the body of mail ID 1.
  • e006 CLOSE closes the selected mail box.
  • e007 LOGOUT logs you off again.

Disconnect

writer.Close();
reader.Close();
stream.Close();
connection.Close();