Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.rastech.meta.google
- import akka.actor._
- import akka.event.slf4j.Logger
- import akka.pattern.ask
- import akka.util.Timeout
- import scala.annotation.tailrec
- import scala.concurrent._
- import scala.concurrent.duration._
- import scala.util.{Try, Success, Failure}
- import spray.client.pipelining._
- import spray.http._
- import spray.http.ContentTypes._
- import spray.http.HttpHeaders._
- import spray.http.HttpMethods._
- import spray.http.MediaTypes._
- import spray.http.Uri._
- import spray.httpx.SprayJsonSupport._
- import spray.httpx.marshalling._
- import spray.json._
- import spray.routing.HttpServiceActor
- import com.google.code.samples.oauth2.OAuth2Authenticator
- import com.sun.mail.imap.IMAPFolder
- import javax.mail.{Message, Multipart, BodyPart, Part, AuthenticationFailedException}
- import java.io.{InputStream, File}
- import com.rastech.meta.util.GrizzledAkkaLogging
- case object Start
- class GmailUserActor(gmailAuthToken: String,
- email: String,
- metaId: String) extends Actor with GrizzledAkkaLogging {
- // This may be not good security wise, using personal emails in log messages
- val logName = email
- val imapPort = 993
- val gmailImapServerUrl = "imap.gmail.com"
- import context.dispatcher
- // Google cancel's idles after 8 minutes without error
- val idleDuration = 5.minutes
- def receive: PartialFunction[Any, Unit] = {
- case Start => {
- Try{
- OAuth2Authenticator.connectToImap(gmailImapServerUrl,
- imapPort,
- email,
- gmailAuthToken,
- true)
- } match {
- case Success(imapStore) => {
- val inbox = imapStore.getFolder("inbox").asInstanceOf[IMAPFolder]
- process(Set(), inbox) //TODO process should return a Try/Failure
- }
- case Failure(ex) => ex match {
- case afe: AuthenticationFailedException => {
- log.error(s"Could not authenticate user info: $ex")
- // TODO: send error back to application layer
- }
- case ex => {
- log.error(s"There was an error trying to connect to the folder: $ex")
- }
- }
- }
- // Infinite loop to watch and process all the messages in a IMAPFolder
- // This annotation makes the compiler emit a warning if the following recursive function
- // can't have the tailrecursion optimization applied to it
- @tailrec
- def process(processedMessages: Set[Message], folder: IMAPFolder): Unit = {
- if(!folder.isOpen()){
- folder.open(javax.mail.Folder.READ_ONLY)
- }
- // Google cancels idle after 8 minutes but the folder.idle keeps blocking, so this
- // Await constructs makes it time out after the idleDuration amount of time
- // See: http://stackoverflow.com/a/16004749/3149857
- Try{Await.result(Future{folder.idle(true)}, idleDuration)} match {
- case Success(()) => { // Dat unit
- log.debug(s"Idle did not return before $idleDuration")
- val allMessages = folder.getMessages.toSet
- val newMessages = allMessages diff processedMessages
- val newAttachments = newMessages.flatMap(getMessageAttachments)
- newAttachments.foreach{ case Attachment(name, is) =>
- log.debug(s"Found attachment with name $name")
- processAttachment(name, is)
- }
- process(allMessages, folder)
- }
- case Failure(_) => {
- log.debug(s"Idle did not return before $idleDuration")
- process(processedMessages, folder)
- }
- }
- }
- }
- }
- private case class Attachment(filename: String, inputStream: InputStream)
- // Provides the nice collection api stuff for the bodyparts of a Multipart object
- private case class BodyPartTraverser(mp: Multipart) extends Traversable[BodyPart] {
- def foreach[U](f: BodyPart => U): Unit = {
- for (i <- 0 until mp.getCount) {
- f(mp.getBodyPart(i))
- }
- }
- }
- // Scala version of javamail code to get all attachements for a message
- // See: http://stackoverflow.com/a/18228443/3149857
- private def getMessageAttachments(message: Message): List[Attachment] = {
- message.getContent match {
- case multipart: Multipart => {
- getAttachments(List(), BodyPartTraverser(multipart).toList)
- }
- // Only multipart messages have attachments
- case _ => List()
- }
- }
- @tailrec
- private def getAttachments(attachments: List[Attachment],
- parts: List[BodyPart]): List[Attachment] = {
- parts match {
- case Nil => attachments
- case part :: remainingParts => {
- part.getContent match {
- // Syntax for if either type matches
- case (_ : InputStream | _ : String) => {
- // These parts represent actual attachements
- // TODO: Use some option monad magic to make this cleaner
- val fileNameBlank = Option(part.getFileName).map{ _.trim.isEmpty }.getOrElse(true)
- if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition) || !fileNameBlank ) {
- val attachment = Attachment(part.getFileName, part.getInputStream)
- getAttachments(attachment :: attachments, remainingParts)
- } else {
- getAttachments(attachments, remainingParts)
- }
- }
- case multipart: Multipart => {
- // This part represenets more parts that might have attachments themselves
- // TODO: Find a collection type that has better append/++ performance
- getAttachments(attachments, remainingParts ++ BodyPartTraverser(multipart))
- }
- case _ => {
- // Any other part isn't an attachment and can be ignored
- getAttachments(attachments, remainingParts)
- }
- }
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement