lib/result.ex

defmodule Speedtest.Result do
  import Speedtest.Decoder

  alias Speedtest.Result

  @default_key "297aae72"

  @moduledoc """
  A speedtest result.
  """

  defstruct download: nil,
            upload: nil,
            ping: nil,
            server: nil,
            client: nil,
            timestamp: nil,
            bytes_received: 0,
            bytes_sent: 0,
            share: nil

  @doc """
  Create a result from a speedtest
  """
  def create(speedtest, {upload_reply, download_reply}) do
    upload_times =
      Enum.map(upload_reply, fn x ->
        x.elapsed_time
      end)

    upload_sizes =
      Enum.map(upload_reply, fn x ->
        to_integer(x.bytes)
      end)

    upload_total_time_in_sec = Enum.sum(upload_times)

    upload_size_total_bytes = Enum.sum(upload_sizes)

    upload_size_total_mb = upload_size_total_bytes / 1_000_000

    upload_size_total_mbit = upload_size_total_mb * 8

    upload = upload_size_total_mbit / upload_total_time_in_sec

    upload_avg_sec = upload_total_time_in_sec / Enum.count(upload_reply)

    upload_bps = upload_size_total_bytes / upload_avg_sec

    download_times =
      Enum.map(download_reply, fn x ->
        x.elapsed_time
      end)

    download_sizes =
      Enum.map(download_reply, fn x ->
        to_integer(x.bytes)
      end)

    download_time_in_sec = Enum.sum(download_times)

    download_size_total_bytes = Enum.sum(download_sizes)

    download_size_total_mb = download_size_total_bytes / 1_000_000

    download_size_total_mbit = download_size_total_mb * 8

    download = download_size_total_mbit / download_time_in_sec

    download_avg_sec = download_time_in_sec / Enum.count(download_reply)

    download_bps = download_size_total_bytes / download_avg_sec

    client = %{speedtest.config.client | ispdlavg: trunc(Float.round(download_bps))}
    client = %{client | ispulavg: trunc(Float.round(upload_bps))}

    result = %Result{
      download: Float.round(download, 2),
      upload: Float.round(upload, 2),
      ping: speedtest.selected_server.ping,
      server: speedtest.selected_server,
      client: client,
      timestamp: DateTime.utc_now(),
      bytes_received: download_size_total_bytes,
      bytes_sent: upload_size_total_bytes,
      share: nil
    }

    reply = %{speedtest | result: result}

    {:ok, reply}
  end

  @doc """
  Share the results with speedtest.net
  """
  def share(%Result{} = result) do
    config_key = Application.get_env(:speedtest, :key)

    key =
      case config_key do
        nil ->
          @default_key

        _ ->
          config_key
      end

    {_, _, ping} = result.ping

    hash =
      :crypto.hash(
        :md5,
        to_string(ping) <>
          "-" <> to_string(result.upload) <> "-" <> to_string(result.download) <> "-" <> key
      )
      |> Base.encode16()

    download = round(result.download)
    ping = round(ping)
    upload = round(result.upload)

    api_data = [
      "recommendedserverid=" <> to_string(result.server.id),
      "ping=" <> to_string(ping),
      "screenresolution=",
      "promo=",
      "download=" <> to_string(download),
      "screendpi=",
      "upload=" <> to_string(upload),
      "testmethod=http",
      "hash=" <> hash,
      "touchscreen=none",
      "startmode=pingselect",
      "accuracy=1",
      "bytesreceived=" <> to_string(result.bytes_received),
      "bytessent=" <> to_string(result.bytes_sent),
      "serverid=" <> to_string(result.server.id)
    ]

    url = "https://www.speedtest.net/api/api.php"
    headers = [{"Referer", "http://c.speedtest.net/flash/speedtest.swf"}]
    body = Enum.join(api_data, "&")
    {_, response} = HTTPoison.post(url, body, headers)

    res = Regex.run(~r{resultid=(.)}, response.body)

    image = List.last(res)

    share = "http://www.speedtest.net/result/" <> image <> ".png"

    %{result | share: share}
  end
end