Skip to content

Commit

Permalink
Upgrade to version 2.
Browse files Browse the repository at this point in the history
  • Loading branch information
m242 committed Sep 11, 2014
1 parent c7b9262 commit 4ad2ca0
Show file tree
Hide file tree
Showing 83 changed files with 2,654 additions and 2,574 deletions.
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ senders and data
Requirements
------------

* [SBT 0.12+](http://www.scala-sbt.org/)
* [PlayFramework 2.1.0+](http://www.playframework.com/)
* [Redis 2.4+](http://redis.io/)
* [SBT 0.13+](http://www.scala-sbt.org/)
* [PlayFramework 2.3.4+](http://www.playframework.com/)
* [Redis 2.6+](http://redis.io/)

For hardware, an Amazon EC2 small instance should be good to begin.
MailDrop should take 512M of RAM for the SMTP module, 512M of RAM for
Expand Down Expand Up @@ -79,3 +79,30 @@ again to specify a custom configuration use start
-Dconfig.file=/path/to/your/application.conf


Changelog
---------

Version 2.0 is a rewrite of the internals of MailDrop. The site itself has
the same look and feel, but the way messages are dealt with is slightly
different.

The primary change is a switch to
[rediscala](https://github.com/etaty/rediscala) to connect to the Redis
instance. rediscala is a Reactive driver and performs much better than
the old Redis client.

Next, many of the actors and supervisors have been tossed in favor of
simple Scala futures. This has simplified the codebase while still
allowing for high performance-- MailDrop just won't be able to be as
finely tuned (custom dispatchers, guaranteed message delivery, etc).

Messages are now stored in Redis in json format instead of as serialized
ByteStrings. The overhead of json parsing on the website is minimal and
allows for a better overall view of the data inside Redis. The string
compression has been removed, requiring a bit more RAM for your Redis
instance, but again this should be a minimal impact.

Last, Play has been updated to the 2.3 branch, along with automatic
asset versioning and a much cleaner way to cache assets. Website
performance should be increased vs. the previous version of MailDrop.

15 changes: 15 additions & 0 deletions common/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
logs
project/project
project/target
target
tmp
.history
dist
/.idea
/*.iml
/out
/.idea_modules
/.classpath
/.project
/RUNNING_PID
/.settings
17 changes: 17 additions & 0 deletions common/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name := "common"

version := "2.0"

scalaVersion := "2.11.2"

resolvers ++= Seq(
"rediscala" at "http://dl.bintray.com/etaty/maven",
"Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
)

libraryDependencies ++= Seq(
"com.typesafe" % "config" % "1.2.1",
"com.typesafe.akka" % "akka-actor_2.11" % "2.3.6",
"com.etaty.rediscala" %% "rediscala" % "1.3.1",
"com.typesafe.play" %% "play-json" % "2.3.4"
)
1 change: 1 addition & 0 deletions common/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logLevel := Level.Warn
47 changes: 47 additions & 0 deletions common/src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
maildrop {

server {
bind-address = "0.0.0.0"
bind-port = 25000
command-delay = 2s
connection-timeout = 60s
max-connections = 200
}

sender {
greylist {
response = "You have been temporarily greylisted. Please try back later."
time = 5m
}
dnsbl = [ "zen.spamhaus.org.", "bl.mailspike.net.", "bb.barracudacentral.org.", "psbl.surriel.com.", "dnsbl.sorbs.net.", "bl.score.senderscore.com" ]
spf {
response = "Invalid sender - see http://www.openspf.org/Introduction"
}
cache-time = 24h
}

recipient {
max-addr-length = 50
response = "That is an invalid recipient."
}

data {
alt-inbox-modifier = 1234567890
alt-inbox-prefix = "D-"
max-message-size = 100k
max-message-reason = "The message is too large (more than 100k)."
}

mailbox {
max-length = 10
expires = 24h
}

redis {
host = "localhost"
port = 6379
// password = "foo"
// db = 2
}

}
12 changes: 12 additions & 0 deletions common/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%level] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
20 changes: 0 additions & 20 deletions common/src/main/scala/com/heluna/cache/Redis.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.heluna.util
package com.heluna.maildrop.util

/**
* Created with IntelliJ IDEA.
* common com.heluna.maildrop
* User: markbe
* Date: 5/15/13
* Time: 9:41 AM
* Date: 9/10/14
* Time: 9:39 AM
*/

object AltInboxUtil {
object AltInbox {

val modifier = BigInt(MailDropConfig.getLong("maildrop.data.alt-inbox-modifier").getOrElse(0L).toString)
val prefix = MailDropConfig.getString("maildrop.data.alt-inbox-prefix").getOrElse("D-")
val prefix = MailDropConfig("maildrop.data.alt-inbox-prefix").getOrElse("D-")

def fromShort(shortId: String) = BigInt(shortId, 36)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.heluna.maildrop.util

import java.io.File
import java.util.concurrent.TimeUnit
import com.typesafe.config.{Config, ConfigFactory}
import scala.util.Try

/**
* common com.heluna.maildrop
* User: markbe
* Date: 9/10/14
* Time: 9:39 AM
*/

object MailDropConfig {

val config = Option(System.getProperty("config.file")).fold[Config](ConfigFactory.load())(f => ConfigFactory.parseFile(new File(f)).withFallback(ConfigFactory.load()))

def getBytes(key: String): Option[Long] = Try(Option(config.getBytes(key).toLong)).toOption.flatten

def getInt(key: String): Option[Int] = Try(Option(config.getInt(key))).toOption.flatten

def getLong(key: String): Option[Long] = Try(Option(config.getLong(key))).toOption.flatten

def getMilliseconds(key: String): Option[Long] = Try(Option(config.getDuration(key, TimeUnit.MILLISECONDS))).toOption.flatten

def getSeconds(key: String): Option[Long] = Try(Option(config.getDuration(key, TimeUnit.SECONDS))).toOption.flatten

def getString(key: String): Option[String] = Try(Option(config.getString(key))).toOption.flatten

def getStringList(key: String): List[String] = Try(getStringList(key)).toOption.getOrElse(List[String]())

def apply(key: String) = getString(key)

}
56 changes: 56 additions & 0 deletions common/src/main/scala/com/heluna/maildrop/util/Mailbox.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.heluna.maildrop.util

import java.util.Date
import play.api.libs.json.{JsValue, Json}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.Random

/**
* smtp com.heluna.maildrop.util
* User: markbe
* Date: 9/10/14
* Time: 11:53 AM
*/

object Mailbox {

val maxLength = MailDropConfig.getInt("maildrop.mailbox.max-length").getOrElse(10)
val expiry = MailDropConfig.getSeconds("maildrop.mailbox.expires").getOrElse(86400L)

def key(recipient: String) = "mailbox:" + recipient.toLowerCase

def add(sender: String, recipient: String, subject: String, date: Date = new Date(), body: String): Unit = {
val id = shortId
val rkey = key(recipient)
val json = Json.obj(
"id" -> id,
"sender" -> sender,
"recipient" -> recipient,
"subject" -> subject,
"date" -> date.getTime,
"body" -> body
)
Redis.client.lpush(rkey, Json.stringify(json))
Redis.client.ltrim(rkey, 0, maxLength - 1)
Redis.client.expire(rkey, expiry)
}

def list(recipient: String): Future[List[JsValue]] =
Redis.client.lrange[String](key(recipient), 0, maxLength).map(seq => seq.map(json => Json.parse(json)).toList)

def get(recipient: String, id: String): Future[Option[JsValue]] =
list(recipient).map(messages => messages.find(json => (json \ "id").as[String] == id))

def delete(recipient: String, id: String): Unit = {
val rkey = key(recipient)
list(recipient).foreach(messages => {
val idx = messages.indexWhere(json => (json \ "id").as[String] == id)
Redis.client.lset(rkey, idx, "__deleted__")
Redis.client.lrem(rkey, 0, "__deleted__")
})
}

def shortId = Random.alphanumeric.take(6).mkString

}
38 changes: 38 additions & 0 deletions common/src/main/scala/com/heluna/maildrop/util/Metrics.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.heluna.maildrop.util

import java.text.SimpleDateFormat
import java.util.Date
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

/**
* smtp com.heluna.maildrop.util
* User: markbe
* Date: 9/10/14
* Time: 11:26 AM
*/

object Metrics {

val BLOCKEDCHANNEL = "blocked-channel"
val sdf = new SimpleDateFormat("yyyy/MM/dd")
def key = sdf.format(new Date())

def connection(): Unit = {
Redis.client.incr(key + "/connection")
}

def message(): Unit = {
Redis.client.incr(key + "/message")
}

def blocked(): Unit = {
Redis.client.incr(key + "/blocked")
Redis.client.incr("blocked") onSuccess {
case blocked => Redis.client.publish(BLOCKEDCHANNEL, blocked.toString)
}
}

def getBlocked = Redis.client.get("blocked").map(_.map(_.utf8String.toLong).getOrElse(0L))

}
28 changes: 28 additions & 0 deletions common/src/main/scala/com/heluna/maildrop/util/Redis.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.heluna.maildrop.util

import java.net.InetSocketAddress

import akka.actor.ActorSystem
import redis.RedisClient
import redis.actors.RedisSubscriberActor
import redis.api.pubsub.{Message, PMessage}

/**
* smtp com.heluna.maildrop.util
* User: markbe
* Date: 9/10/14
* Time: 11:09 AM
*/

object Redis {

implicit val redisSystem = ActorSystem()

val hostname = MailDropConfig("maildrop.redis.host").getOrElse("localhost")
val port = MailDropConfig.getInt("maildrop.redis.port").getOrElse(6379)
val password = MailDropConfig("maildrop.redis.password")
val db = MailDropConfig.getInt("maildrop.redis.db")

val client = RedisClient(hostname, port, password, db)

}
9 changes: 0 additions & 9 deletions common/src/main/scala/com/heluna/model/Continue.scala

This file was deleted.

10 changes: 0 additions & 10 deletions common/src/main/scala/com/heluna/model/Greylist.scala

This file was deleted.

15 changes: 0 additions & 15 deletions common/src/main/scala/com/heluna/model/Message.scala

This file was deleted.

10 changes: 0 additions & 10 deletions common/src/main/scala/com/heluna/model/NewTo.scala

This file was deleted.

Loading

0 comments on commit 4ad2ca0

Please sign in to comment.