From eda99fa903cf1177b68ec8cef148644eeeff8835 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Wed, 10 Nov 2021 10:10:43 -0500 Subject: [PATCH] Add attribution field and handle async duration race --- lib/live_beats/media_library.ex | 9 ++++- lib/live_beats/media_library/song.ex | 14 ++++++- lib/live_beats_web/live/live_helpers.ex | 2 +- lib/live_beats_web/live/player_live.ex | 2 +- lib/live_beats_web/live/song_live/index.ex | 1 + .../live/song_live/song_entry_component.ex | 13 +++++++ .../live/song_live/song_row_component.ex | 2 +- .../live/song_live/upload_form_component.ex | 38 ++++++++++++++----- .../templates/layout/live.html.heex | 8 +++- .../templates/layout/root.html.heex | 3 -- .../20211027201102_create_songs.exs | 1 + 11 files changed, 72 insertions(+), 21 deletions(-) diff --git a/lib/live_beats/media_library.ex b/lib/live_beats/media_library.ex index 5759b1a..7ae0912 100644 --- a/lib/live_beats/media_library.ex +++ b/lib/live_beats/media_library.ex @@ -49,7 +49,7 @@ defmodule LiveBeats.MediaLibrary do stopped_query = from s in Song, - where: s.user_id == ^song.user_id and s.status == :playing, + where: s.user_id == ^song.user_id and s.status in [:playing, :paused], update: [set: [status: :stopped]] {:ok, %{now_playing: new_song}} = @@ -89,7 +89,12 @@ defmodule LiveBeats.MediaLibrary do end def put_stats(%Ecto.Changeset{} = changeset, %MP3Stat{} = stat) do - Ecto.Changeset.put_change(changeset, :duration, stat.duration) + chset = Song.put_duration(changeset, stat.duration) + if error = chset.errors[:duration] do + {:error, %{duration: error}} + else + {:ok, chset} + end end def import_songs(%Accounts.User{} = user, changesets, consume_file) diff --git a/lib/live_beats/media_library/song.ex b/lib/live_beats/media_library/song.ex index 206940b..0780bc2 100644 --- a/lib/live_beats/media_library/song.ex +++ b/lib/live_beats/media_library/song.ex @@ -15,6 +15,7 @@ defmodule LiveBeats.MediaLibrary.Song do field :duration, :integer field :status, Ecto.Enum, values: [stopped: 1, playing: 2, paused: 3] field :title, :string + field :attribution, :string field :mp3_url, :string field :mp3_filepath, :string field :mp3_filename, :string @@ -31,15 +32,24 @@ defmodule LiveBeats.MediaLibrary.Song do @doc false def changeset(song, attrs) do song - |> cast(attrs, [:album_artist, :artist, :title, :date_recorded, :date_released]) + |> cast(attrs, [:album_artist, :artist, :title, :attribution, :date_recorded, :date_released]) |> validate_required([:artist, :title]) - |> validate_number(:duration, greater_than: 0, less_than: 1200) end def put_user(%Ecto.Changeset{} = changeset, %Accounts.User{} = user) do put_assoc(changeset, :user, user) end + def put_duration(%Ecto.Changeset{} = changeset, duration) when is_integer(duration) do + changeset + |> Ecto.Changeset.change(%{duration: duration}) + |> Ecto.Changeset.validate_number(:duration, + greater_than: 0, + less_than: 1200, + message: "must be less than 20 minutes" + ) + end + def put_mp3_path(%Ecto.Changeset{} = changeset) do if changeset.valid? do filename = Ecto.UUID.generate() <> ".mp3" diff --git a/lib/live_beats_web/live/live_helpers.ex b/lib/live_beats_web/live/live_helpers.ex index 4a7daff..1c434ec 100644 --- a/lib/live_beats_web/live/live_helpers.ex +++ b/lib/live_beats_web/live/live_helpers.ex @@ -315,7 +315,7 @@ defmodule LiveBeatsWeb.LiveHelpers do <%= for {row, i} <- Enum.with_index(@rows) do %> <%= for col <- @col do %> - +
<%= render_slot(col, row) %>
diff --git a/lib/live_beats_web/live/player_live.ex b/lib/live_beats_web/live/player_live.ex index cf115e9..1a03f28 100644 --- a/lib/live_beats_web/live/player_live.ex +++ b/lib/live_beats_web/live/player_live.ex @@ -16,7 +16,7 @@ defmodule LiveBeatsWeb.PlayerLive do
-
+

<%= if @song, do: @song.title, else: raw(" ") %>

diff --git a/lib/live_beats_web/live/song_live/index.ex b/lib/live_beats_web/live/song_live/index.ex index f697c81..e8924ca 100644 --- a/lib/live_beats_web/live/song_live/index.ex +++ b/lib/live_beats_web/live/song_live/index.ex @@ -33,6 +33,7 @@ defmodule LiveBeatsWeb.SongLive.Index do > <:col let={%{song: song}} label="Title"><%= song.title %> <:col let={%{song: song}} label="Artist"><%= song.artist %> + <:col let={%{song: song}} label="Attribution" class="max-w-5xl break-words text-gray-600 font-light"><%= song.attribution %> <:col let={%{song: song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %> <:col let={%{song: song}} label=""> <.link phx-click={show_modal("delete-modal-#{song.id}")} class="inline-flex items-center px-3 py-2 text-sm leading-4 font-medium"> diff --git a/lib/live_beats_web/live/song_live/song_entry_component.ex b/lib/live_beats_web/live/song_live/song_entry_component.ex index 0d0b010..a2d2d1b 100644 --- a/lib/live_beats_web/live/song_live/song_entry_component.ex +++ b/lib/live_beats_web/live/song_live/song_entry_component.ex @@ -33,6 +33,18 @@ defmodule LiveBeatsWeb.SongLive.SongEntryComponent do <.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors} class="-mt-1"/> <.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors} class="-mt-1"/>
+
+ + +
+
+ <.error input_name={"songs[#{@ref}][attribution]"} field={:attribution} errors={@errors} class="-mt-1"/> +
@@ -51,6 +63,7 @@ defmodule LiveBeatsWeb.SongLive.SongEntryComponent do |> assign(title: Ecto.Changeset.get_field(changeset, :title)) |> assign(artist: Ecto.Changeset.get_field(changeset, :artist)) |> assign(duration: Ecto.Changeset.get_field(changeset, :duration)) + |> assign(attribution: Ecto.Changeset.get_field(changeset, :attribution)) |> assign_new(:progress, fn -> 0 end)} end end diff --git a/lib/live_beats_web/live/song_live/song_row_component.ex b/lib/live_beats_web/live/song_live/song_row_component.ex index 6494e89..5074efb 100644 --- a/lib/live_beats_web/live/song_live/song_row_component.ex +++ b/lib/live_beats_web/live/song_live/song_row_component.ex @@ -10,7 +10,7 @@ defmodule LiveBeatsWeb.SongLive.SongRowComponent do <%= for {col, i} <- Enum.with_index(@col) do %>
diff --git a/lib/live_beats_web/live/song_live/upload_form_component.ex b/lib/live_beats_web/live/song_live/upload_form_component.ex index 4e10097..c9c1512 100644 --- a/lib/live_beats_web/live/song_live/upload_form_component.ex +++ b/lib/live_beats_web/live/song_live/upload_form_component.ex @@ -46,18 +46,26 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do %{current_user: current_user} = socket.assigns changesets = socket.assigns.changesets - case MediaLibrary.import_songs(current_user, changesets, &consume_entry(socket, &1, &2)) do - {:ok, songs} -> - {:noreply, - socket - |> put_flash(:info, "#{map_size(songs)} song(s) uploaded") - |> push_redirect(to: Routes.song_index_path(socket, :index))} + if pending_stats?(socket) do + {:noreply, socket} + else + case MediaLibrary.import_songs(current_user, changesets, &consume_entry(socket, &1, &2)) do + {:ok, songs} -> + {:noreply, + socket + |> put_flash(:info, "#{map_size(songs)} song(s) uploaded") + |> push_redirect(to: home_path(socket))} - {:error, _reason} -> - {:noreply, socket} + {:error, _reason} -> + {:noreply, socket} + end end end + defp pending_stats?(socket) do + Enum.find(socket.assigns.changesets, fn {_ref, chset} -> !chset.changes[:duration] end) + end + defp consume_entry(socket, ref, store_func) when is_function(store_func) do {entries, []} = uploaded_entries(socket, :mp3) entry = Enum.find(entries, fn entry -> entry.ref == ref end) @@ -121,6 +129,7 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do consume_uploaded_entry(socket, entry, fn %{path: path} -> Task.Supervisor.start_child(LiveBeats.TaskSupervisor, fn -> + Process.sleep(5000) send_update(lv, __MODULE__, id: socket.assigns.id, action: {:duration, entry.ref, LiveBeats.MP3Stat.parse(path)} @@ -136,9 +145,20 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do defp file_error(%{kind: :not_accepted} = assigns), do: ~H|not a valid MP3 file| defp file_error(%{kind: :too_many_files} = assigns), do: ~H|too many files| + defp file_error(%{kind: {msg, opts}} = assigns) when is_binary(msg) and is_list(opts) do + ~H|<%= LiveBeatsWeb.ErrorHelpers.translate_error(@kind) %>| + end + defp put_stats(socket, entry_ref, %MP3Stat{} = stat) do if changeset = get_changeset(socket, entry_ref) do - update_changeset(socket, MediaLibrary.put_stats(changeset, stat), entry_ref) + case MediaLibrary.put_stats(changeset, stat) do + {:ok, new_changeset} -> + update_changeset(socket, new_changeset, entry_ref) + + {:error, %{duration: error}} -> + IO.inspect({:duration, error}) + cancel_changeset_upload(socket, entry_ref, error) + end else socket end diff --git a/lib/live_beats_web/templates/layout/live.html.heex b/lib/live_beats_web/templates/layout/live.html.heex index 0a5cdae..f0e1fd0 100644 --- a/lib/live_beats_web/templates/layout/live.html.heex +++ b/lib/live_beats_web/templates/layout/live.html.heex @@ -1,4 +1,10 @@
+ <.live_component module={LiveBeatsWeb.LayoutComponent} id="layout" /> + + <%= if @current_user do %> + <%= live_render(@socket, LiveBeatsWeb.PlayerLive, id: "player", session: %{}, sticky: true) %> + <% end %> + @@ -7,7 +13,5 @@ phx-click="lv:clear-flash" phx-value-key="error"><%= live_flash(@flash, :error) %>

- <.live_component module={LiveBeatsWeb.LayoutComponent} id="layout" /> - <%= @inner_content %>
diff --git a/lib/live_beats_web/templates/layout/root.html.heex b/lib/live_beats_web/templates/layout/root.html.heex index 50f7490..83cb26d 100644 --- a/lib/live_beats_web/templates/layout/root.html.heex +++ b/lib/live_beats_web/templates/layout/root.html.heex @@ -301,9 +301,6 @@
- <%= if @current_user do %> - <%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %> - <% end %> <%= @inner_content %>
diff --git a/priv/repo/migrations/20211027201102_create_songs.exs b/priv/repo/migrations/20211027201102_create_songs.exs index 3da4df1..7244dea 100644 --- a/priv/repo/migrations/20211027201102_create_songs.exs +++ b/priv/repo/migrations/20211027201102_create_songs.exs @@ -10,6 +10,7 @@ defmodule LiveBeats.Repo.Migrations.CreateSongs do add :played_at, :utc_datetime add :paused_at, :utc_datetime add :title, :string, null: false + add :attribution, :string add :mp3_url, :string, null: false add :mp3_filename, :string, null: false add :mp3_filepath, :string, null: false