patrickdavey/caster

View on GitHub
web/models/vim_cast.ex

Summary

Maintainability
Test Coverage
defmodule Caster.VimCast do
  @moduledoc """
  This is the model for the vimcasts site

  It uses the same underlying table (the "casts" one).

  """
  use Caster.Web, :model

  @allowed_params [:title, :url, :file_location, :episode, :published_at,
                   :viewed, :interesting, :source, :note]
  @required_params [:title, :url, :published_at]
  @source :vimcast

  schema "casts" do
    field :title, :string
    field :url, :string
    field :file_location, :string
    field :episode, :integer
    field :viewed, :boolean, default: false
    field :interesting, :boolean, default: false
    field :source, :string, default: Atom.to_string(@source)
    field :note, :string
    field :published_at, Timex.Ecto.DateTime

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    changes = Dict.merge(%{}, params)
    struct
    |> cast(changes, @allowed_params)
    |> validate_required(@required_params)
  end

  def download_path(%{url: url}) do
    Path.expand("#{Application.get_env(:caster, :root_downloads_directory)}/vimcasts/#{Path.basename(url)}",
                Application.app_dir(:caster, "priv"))
  end

  defmodule Downloader do
    @moduledoc """
    This downloader finds the file at a url and downloads it into a path.
    It does this with a chunked write, however there is no backpressure
    so that could be looked into at somepoint
    """
    require Logger
    alias Caster.Cast
    alias Caster.VimCast
    alias Caster.Repo

    def download!(cast = %Cast{url: url}) do
      Task.Supervisor.async_nolink(Caster.TaskSupervisor, fn ->
        filepath = VimCast.download_path(cast)
        Mix.Generator.create_directory(Path.dirname(filepath))
        file = File.open! filepath, [:append]
        HTTPoison.get!(url, %{}, [stream_to: self])
        rloop({cast, file, filepath})
      end)
    end

    defp rloop({cast, file, filepath})  do
      receive do
        %HTTPoison.AsyncChunk{chunk: chunk} ->
          IO.binwrite(file, chunk)
          rloop({cast, file, filepath})
        %HTTPoison.AsyncEnd{} ->
          :ok = File.close file
          changeset = VimCast.changeset(cast, %{file_location: filepath})
          Repo.update!(changeset)
          # broadcast a downloaded event
          Logger.info "About to broadcast to casts:cast#{cast.id}"
          Caster.Endpoint.broadcast("casts:cast#{cast.id}", "downloaded", %{msg: "downloaded"})
        after
          1_000 ->
            File.close file
            File.rm filepath
            Logger.info "exiting download loop, nothing after 1 second"
        end
    end
  end
end