podbox_ash/lib/podbox/download/asset.ex

165 lines
4.6 KiB
Elixir
Raw Normal View History

2024-05-22 13:17:21 +12:00
defmodule Podbox.Download.Asset do
@moduledoc """
An item in the download queue.
"""
use Ash.Resource,
data_layer: AshSqlite.DataLayer,
domain: Podbox.Download,
notifiers: [Ash.Notifier.PubSub]
alias Ash.Query
require Ash.Query
@type t :: Ash.Resource.record()
attributes do
uuid_primary_key :id
attribute :uri, :string, allow_nil?: false
attribute :state, :atom,
allow_nil?: false,
default: :queued,
constraints: [one_of: [:queued, :dequeued, :downloading, :complete, :failed]]
attribute :remaining_retries, :integer,
allow_nil?: false,
constraints: [min: 0]
attribute :total_bytes, :integer,
allow_nil?: true,
constraints: [min: 0]
attribute :transferred_bytes, :integer,
allow_nil?: true,
constraints: [min: 0]
attribute :content_type, :string, allow_nil?: true
attribute :error_detail, :string, allow_nil?: true
attribute :headers, :map, allow_nil?: false, public?: true, sensitive?: true, default: %{}
create_timestamp :inserted_at
update_timestamp :updated_at
end
sqlite do
table "download_assets"
repo Podbox.Repo
end
pub_sub do
module Podbox.Download.PubSub
prefix "asset"
publish :enqueue, "queued"
publish :started, "started"
publish :pause, "paused"
publish :failed, "failed"
publish :progress, "progress"
publish :complete, "complete"
end
actions do
create :enqueue do
argument :uri, :string, allow_nil?: true, public?: true
argument :headers, :map, allow_nil?: true, public?: true, sensitive?: true
argument :retries, :integer, allow_nil?: true, default: 5, public?: true
change set_attribute(:uri, arg(:uri))
change set_attribute(:headers, arg(:headers)), where: [present(:headers)]
change set_attribute(:remaining_retries, arg(:retries))
change set_attribute(:state, :queued)
end
update :started do
argument :total_bytes, :integer, allow_nil?: true, constraints: [min: 0], public?: true
argument :transferred_bytes, :integer,
allow_nil?: true,
constraints: [min: 0],
public?: true
argument :content_type, :string, allow_nil?: true, public?: true
change set_attribute(:state, :downloading)
change set_attribute(:content_type, arg(:content_type)), where: [present(:content_type)]
change set_attribute(:total_bytes, arg(:total_bytes)), where: [present(:total_bytes)]
change set_attribute(:transferred_bytes, arg(:transferred_bytes)),
where: [present(:transferred_bytes)]
end
update :pause do
change set_attribute(:state, :queued)
end
update :failed do
argument :error_detail, :string, allow_nil?: false, public?: true
change set_attribute(:error_detail, arg(:error_detail))
change set_attribute(:state, :failed), where: [attribute_equals(:remaining_retries, 0)]
change set_attribute(:state, :queued), where: [compare(:remaining_retries, greater_than: 0)]
change increment(:remaining_retries, amount: -1),
where: [compare(:remaining_retries, greater_than: 0)]
end
update :progress do
argument :transferred_bytes, :integer, allow_nil?: false, constraints: [min: 0]
change set_attribute(:transferred_bytes, arg(:transferred_bytes))
end
update :complete do
argument :transferred_bytes, :integer,
allow_nil?: true,
constraints: [min: 0],
public?: true
change set_attribute(:state, :complete)
change set_attribute(:transferred_bytes, arg(:transferred_bytes)),
where: [present(:transferred_bytes)]
end
read :queued do
argument :limit, :integer, allow_nil?: true, constraints: [min: 0]
prepare build(sort: [updated_at: :desc], limit: arg(:limit))
end
action :dequeue, {:array, :struct} do
constraints items: [instance_of: __MODULE__]
argument :limit, :integer, allow_nil?: true, constraints: [min: 0]
run &dequeue/2
end
destroy :destroy do
primary? true
end
read :read do
primary? true
pagination keyset?: true, required?: false
end
update :dequeued do
change set_attribute(:state, :dequeued)
end
end
@doc false
def dequeue(input, _) do
__MODULE__
|> Query.for_read(:read)
|> Query.sort(updated_at: :desc)
|> Query.filter(state == :queued || (state == :dequeued && updated_at <= ago(2, :hour)))
|> Query.limit(input.arguments.limit)
|> Ash.bulk_update(:dequeued, %{}, return_records?: true)
|> case do
%{status: :success, records: records} -> {:ok, records}
%{errors: errors} -> {:error, errors}
end
end
end