Packages
ex_money
1.1.0
6.1.1
6.1.0
6.0.0
6.0.0-rc.0
5.24.2
5.24.1
5.24.0
retired
5.23.0
5.22.0
5.21.0
5.20.0
5.19.2
5.19.1
5.19.0
5.18.0
5.17.2
5.17.1
5.17.0
5.16.1
5.16.0
5.15.4
5.15.3
5.15.2
5.15.1
5.15.0
5.14.1
5.13.0
5.12.4
5.12.3
5.12.2
5.12.1
5.12.0
5.11.0
5.10.0
5.9.0
5.8.0
5.7.4
5.7.3
5.7.2
5.7.1
5.7.0
5.6.0
5.5.5
5.5.4
5.5.3
5.5.2
5.5.1
5.5.0
5.4.1
5.4.0
5.4.0-rc.0
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.0
5.0.2
5.0.1
5.0.0
retired
4.4.3
4.4.2
retired
4.4.1
4.4.0
4.3.0
4.2.2
4.2.1
4.2.0
4.1.0
4.0.0
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.1
3.3.0
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.0
3.0.0
2.12.2
2.12.1
retired
2.12.0
retired
2.11.0
2.10.0
2.9.1
2.9.0
2.8.0
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.0
2.3.0
2.2.2
2.2.1
2.2.0
2.1.0
2.1.0-rc.1
retired
2.1.0-rc.0
retired
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
1.0.0
1.0.0-rc.0
retired
0.9.0
retired
0.8.5
retired
0.8.4
retired
0.8.3
retired
0.8.2
retired
0.8.1
retired
0.8.0
retired
0.7.0
retired
0.6.2
retired
0.6.1
retired
0.6.0
retired
0.5.3
retired
0.5.2
retired
0.5.1
retired
0.5.0
retired
0.4.3
retired
0.4.2
retired
0.4.1
retired
0.4.0
retired
0.3.0
retired
0.2.0
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
0.0.16
retired
0.0.15
retired
0.0.14
retired
0.0.13
retired
0.0.12
retired
0.0.11
retired
0.0.10
retired
0.0.9
retired
0.0.7
retired
0.0.6
retired
0.0.5
retired
0.0.4
retired
0.0.3
retired
0.0.2
retired
Money functions for operations on and localization of a money data type with support for ISO 4217 currencies and ISO 24165 digial tokens (crypto currencies).
Current section
Files
Jump to
Current section
Files
lib/money/exchange_rates/exchange_rates_retriever.ex
defmodule Money.ExchangeRates.Retriever do
@moduledoc """
Implements a `GenServer` to retrieve exchange rates from
a configured retrieveal module on a periodic basis. By default exchange
rates are retrieved from [Open Exchange Rates](http://openexchangerates.org).
Retrieved data is stored in an `:ets` table.
By default the period of execution is 5 minutes (300_000 microseconds). The
period of retrieval is configured in `config.exs` or the appropriate
environment configuration. For example:
config :ex_money,
open_exchange_rates_app_id: "app_id_string",
open_exchange_rates_retrieve_every: 300_000
"""
use GenServer
require Logger
alias Money.ExchangeRates
def start_link(name, config) do
GenServer.start_link(__MODULE__, config, name: name)
end
def init(config) do
log(config, :info, "Starting exchange rate retrieval service")
initialize_ets_table()
log(config, :info, log_init_message(config.retrieve_every))
schedule_work(0)
schedule_work(config.retrieve_every)
if config.preload_historic_rates do
log(config, :info, "Preloading historic rates for #{inspect config.preload_historic_rates}")
schedule_work(config.preload_historic_rates)
end
{:ok, config}
end
def handle_info(:latest_rates, config) do
retrieve_latest_rates(config)
schedule_work(config.retrieve_every)
{:noreply, config}
end
def handle_info({:historic_rates, %Date{calendar: Calendar.ISO} = date}, config) do
retrieve_historic_rates(date, config)
{:noreply, config}
end
def handle_info(message, config) do
Logger.error "Invalid message for ExchangeRates.Retriever: #{inspect message}"
{:noreply, config}
end
def retrieve_latest_rates(%{callback_module: callback_module} = config) do
case ExchangeRates.get_latest_rates(config) do
{:ok, rates} ->
retrieved_at = DateTime.utc_now
store_latest_rates(rates, retrieved_at)
apply(callback_module, :latest_rates_retrieved, [rates, retrieved_at])
log(config, :success, "Retrieved latest exchange rates successfully")
{:ok, rates}
{:error, reason} ->
log(config, :failure, "Could not retrieve latest exchange rates: #{inspect reason}")
{:error, reason}
end
end
def retrieve_historic_rates(%Date{} = date, %{callback_module: callback_module} = config) do
case ExchangeRates.get_historic_rates(date, config) do
{:ok, rates} ->
store_historic_rates(rates, date)
apply(callback_module, :historic_rates_retrieved, [rates, date])
log(config, :success,
"Retrieved historic exchange rates for #{Date.to_string(date)} successfully")
{:ok, rates}
{:error, reason} ->
log(config, :failure, "Could not retrieve historic exchange rates " <>
"for #{Date.to_string(date)}: #{inspect reason}")
{:error, reason}
end
end
defp schedule_work(delay_ms) when is_integer(delay_ms) do
Process.send_after(self(), :latest_rates, delay_ms)
end
defp schedule_work(%Date{calendar: Calendar.ISO} = date) do
Process.send(self(), {:historic_rates, date}, [])
end
defp schedule_work(%Date.Range{} = date_range) do
for date <- date_range do
Process.send(self(), {:historic_rates, date}, [])
end
end
defp schedule_work({%Date{} = from, %Date{} = to}) do
schedule_work(Date.range(from, to))
end
defp schedule_work(date_string) when is_binary(date_string) do
parts = String.split(date_string, "..")
case parts do
[date] -> schedule_work(Date.from_iso8601(date))
[from, to] -> schedule_work({Date.from_iso8601(from), Date.from_iso8601(to)})
end
end
defp initialize_ets_table do
:ets.new(:exchange_rates, [:named_table, read_concurrency: true])
end
defp store_latest_rates(rates, retrieved_at) do
:ets.insert(:exchange_rates, {:latest_rates, rates})
:ets.insert(:exchange_rates, {:last_updated, retrieved_at})
end
defp store_historic_rates(rates, date) do
:ets.insert(:exchange_rates, {date, rates})
end
def log(%{log_levels: log_levels}, key, message) do
case Map.get(log_levels, key) do
nil ->
nil
log_level ->
Logger.log(log_level, message)
end
end
defp log_init_message(every) do
{every, plural_every} = seconds(every)
"Rates will be retrieved now and then every #{every} #{plural_every}."
end
defp seconds(milliseconds) do
seconds = div(milliseconds, 1000)
plural = if seconds == 1, do: "second", else: "seconds"
{seconds, plural}
end
end