Skip to content

Instantly share code, notes, and snippets.

@fbettag
Created July 12, 2014 09:37
Show Gist options
  • Save fbettag/1cca7db7c36c4a706709 to your computer and use it in GitHub Desktop.
Save fbettag/1cca7db7c36c4a706709 to your computer and use it in GitHub Desktop.
Currency Converter in Scala (no rounding yet) based on Yahoo Finance and Twitter Finagle
import java.util.concurrent.atomic.AtomicReference
import com.twitter.finagle.{ http, Service }
import com.twitter.finagle.builder.ClientBuilder
import com.twitter.util.{ Promise, Future }
import org.jboss.netty.handler.codec.http._
import org.jboss.netty.util.CharsetUtil
import net.liftweb.util.Schedule
import net.liftweb.json._
import net.liftweb.common.Logger
import net.liftweb.util.Helpers._
import java.math.{ BigDecimal => BigDec }
object Currencies extends Enumeration with Logger {
val EUR, CHF, USD, GBP, AUD, NZD, CAD = Value
private val baseCurrency = USD
val uri = "http://finance.yahoo.com/webservice/v1/symbols/allcurrencies/quote?format=json&view=basic"
private val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
.codec(http.Http())
.hosts("finance.yahoo.com:80")
.hostConnectionLimit(1)
//.tlsWithoutValidation()
.build()
private def fetch() {
val request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri)
request.headers().add("Host", "finance.yahoo.com")
debug("Requesting " + uri)
val response: Future[HttpResponse] = client(request)
response.onFailure(x => error(x))
response.onSuccess { response =>
val respStr = response.getContent.toString(CharsetUtil.UTF_8)
val json = JsonParser.parseOpt(respStr)
json.map(store) getOrElse {
error("Invalid response: " + respStr)
}
}
Schedule(() => fetch(), 15.minutes)
}
private def store(quotes: JValue) {
debug("Storing")
var quotesM = Map[String, BigDec]()
quotes match {
case jo: JObject =>
jo \ "list" \ "resources" match {
case JArray(resources) =>
resources.map {
case res: JObject =>
val fields = res \ "resource" \ "fields"
val quote = for {
name <- (fields \ "name").extractOpt[String]
price <- (fields \ "price").extractOpt[String].map(new BigDec(_))
if name.matches(".../...")
} yield (name, price)
quote match {
case Some((name, price)) => quotesM ++= Map(name -> price)
case _ => warn("Did not contain a quote!")
}
case _ => warn("Did not contain a resource")
}
case _ => warn("Did not contain a resource!")
}
case _ => warn("Did not contain resources!")
}
if (quotesM.nonEmpty) cachedQuotes.set(quotesM)
}
private val cachedQuotes = new AtomicReference[Map[String, BigDec]](Map())
def convert(amount: Double, from: this.Value, to: this.Value): Option[Double] = {
if (from == to) Some(amount)
else if (from == baseCurrency) {
val pair = from.toString.toUpperCase + "/" + to.toString.toUpperCase
cachedQuotes.get.get(pair).map(x => amount * x.doubleValue())
} else if (to == baseCurrency) {
val pair = to.toString.toUpperCase + "/" + from.toString.toUpperCase
cachedQuotes.get.get(pair).map(x => amount / x.doubleValue())
} else {
val pair1 = baseCurrency.toString.toUpperCase + "/" + from.toString.toUpperCase
val pair2 = baseCurrency.toString.toUpperCase + "/" + to.toString.toUpperCase
for {
fromQuote <- cachedQuotes.get.get(pair1)
toQuote <- cachedQuotes.get.get(pair2)
} yield amount / fromQuote.doubleValue() * toQuote.doubleValue()
}
}
def init() {
fetch()
}
}
@alexanderdean
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment