mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-09-25 21:10:01 +00:00
Compare commits
2 commits
446b861a67
...
115356df2d
Author | SHA1 | Date | |
---|---|---|---|
|
115356df2d | ||
|
50794e1af1 |
5 changed files with 51 additions and 21 deletions
|
@ -163,6 +163,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|
|
||||||
multi =
|
multi =
|
||||||
Ecto.Multi.new()
|
Ecto.Multi.new()
|
||||||
|
|> lock_playlist(user.id)
|
||||||
|> Ecto.Multi.run(:starting_position, fn repo, _changes ->
|
|> Ecto.Multi.run(:starting_position, fn repo, _changes ->
|
||||||
count = repo.one(from s in Song, where: s.user_id == ^user.id, select: count(s.id))
|
count = repo.one(from s in Song, where: s.user_id == ^user.id, select: count(s.id))
|
||||||
{:ok, count - 1}
|
{:ok, count - 1}
|
||||||
|
@ -358,27 +359,28 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|
|
||||||
multi =
|
multi =
|
||||||
Ecto.Multi.new()
|
Ecto.Multi.new()
|
||||||
|> Ecto.Multi.run(:valid_index, fn repo, _changes ->
|
|> lock_playlist(song.user_id)
|
||||||
|
|> Ecto.Multi.run(:index, fn repo, _changes ->
|
||||||
case repo.one(from s in Song, where: s.user_id == ^song.user_id, select: count(s.id)) do
|
case repo.one(from s in Song, where: s.user_id == ^song.user_id, select: count(s.id)) do
|
||||||
count when new_index < count -> {:ok, count}
|
count when new_index < count -> {:ok, new_index}
|
||||||
_count -> {:error, :index_out_of_range}
|
count -> {:ok, count - 1}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> multi_update_all(:dec_positions, fn _ ->
|
|> multi_update_all(:dec_positions, fn %{index: new_index} ->
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.user_id == ^song.user_id and s.id != ^song.id,
|
where: s.user_id == ^song.user_id and s.id != ^song.id,
|
||||||
where: s.position > ^old_index and s.position <= ^new_index,
|
where: s.position > ^old_index and s.position <= ^new_index,
|
||||||
update: [inc: [position: -1]]
|
update: [inc: [position: -1]]
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|> multi_update_all(:inc_positions, fn _ ->
|
|> multi_update_all(:inc_positions, fn %{index: new_index} ->
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.user_id == ^song.user_id and s.id != ^song.id,
|
where: s.user_id == ^song.user_id and s.id != ^song.id,
|
||||||
where: s.position < ^old_index and s.position >= ^new_index,
|
where: s.position < ^old_index and s.position >= ^new_index,
|
||||||
update: [inc: [position: 1]]
|
update: [inc: [position: 1]]
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|> multi_update_all(:position, fn _ ->
|
|> multi_update_all(:position, fn %{index: new_index} ->
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.id == ^song.id,
|
where: s.id == ^song.id,
|
||||||
update: [set: [position: ^new_index]]
|
update: [set: [position: ^new_index]]
|
||||||
|
@ -406,6 +408,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
old_index = song.position
|
old_index = song.position
|
||||||
|
|
||||||
Ecto.Multi.new()
|
Ecto.Multi.new()
|
||||||
|
|> lock_playlist(song.user_id)
|
||||||
|> Ecto.Multi.delete(:delete, song)
|
|> Ecto.Multi.delete(:delete, song)
|
||||||
|> multi_update_all(:dec_positions, fn _ ->
|
|> multi_update_all(:dec_positions, fn _ ->
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
|
@ -533,4 +536,8 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
defp multi_update_all(multi, name, func, opts \\ []) do
|
defp multi_update_all(multi, name, func, opts \\ []) do
|
||||||
Ecto.Multi.update_all(multi, name, func, opts)
|
Ecto.Multi.update_all(multi, name, func, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp lock_playlist(%Ecto.Multi{} = multi, user_id) do
|
||||||
|
Repo.multi_transaction_lock(multi, :playlist, user_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ defmodule LiveBeats.MediaLibrary.Song do
|
||||||
field :date_recorded, :naive_datetime
|
field :date_recorded, :naive_datetime
|
||||||
field :date_released, :naive_datetime
|
field :date_released, :naive_datetime
|
||||||
field :duration, :integer
|
field :duration, :integer
|
||||||
field :status, Ecto.Enum, values: [stopped: 1, playing: 2, paused: 3]
|
field :status, Ecto.Enum, values: [stopped: 1, playing: 2, paused: 3], default: :stopped
|
||||||
field :title, :string
|
field :title, :string
|
||||||
field :attribution, :string
|
field :attribution, :string
|
||||||
field :mp3_url, :string
|
field :mp3_url, :string
|
||||||
|
|
|
@ -4,6 +4,16 @@ defmodule LiveBeats.Repo do
|
||||||
adapter: Ecto.Adapters.Postgres
|
adapter: Ecto.Adapters.Postgres
|
||||||
|
|
||||||
def replica, do: LiveBeats.config([:replica])
|
def replica, do: LiveBeats.config([:replica])
|
||||||
|
|
||||||
|
@locks %{playlist: 1}
|
||||||
|
|
||||||
|
def multi_transaction_lock(multi, scope, id) when is_atom(scope) and is_integer(id) do
|
||||||
|
scope_int = Map.fetch!(@locks, scope)
|
||||||
|
|
||||||
|
Ecto.Multi.run(multi, scope, fn repo, _changes ->
|
||||||
|
repo.query("SELECT pg_advisory_xact_lock(#{scope_int}, #{id})")
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule LiveBeats.ReplicaRepo do
|
defmodule LiveBeats.ReplicaRepo do
|
||||||
|
|
|
@ -582,7 +582,7 @@ defmodule LiveBeatsWeb.CoreComponents do
|
||||||
<tbody
|
<tbody
|
||||||
id={"#{@id}-body"}
|
id={"#{@id}-body"}
|
||||||
class="bg-white divide-y divide-gray-100"
|
class="bg-white divide-y divide-gray-100"
|
||||||
phx-stream={@streamable}
|
phx-update={@streamable && "stream"}
|
||||||
phx-hook={@sortable_drop && "Sortable"}
|
phx-hook={@sortable_drop && "Sortable"}
|
||||||
data-drop={@sortable_drop}
|
data-drop={@sortable_drop}
|
||||||
>
|
>
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
<%= if @owns_profile? do %>
|
<%= if @owns_profile? do %>
|
||||||
(you)
|
(you)
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= @songs_count %>
|
||||||
</div>
|
</div>
|
||||||
<.link href={@profile.external_homepage_url} target="_blank" class="text-sm text-gray-600">
|
<.link href={@profile.external_homepage_url} target="_blank" class="text-sm text-gray-600">
|
||||||
<.icon name={:code} /> <span class=""><%= url_text(@profile.external_homepage_url) %></span>
|
<.icon name={:code} /> <span class=""><%= url_text(@profile.external_homepage_url) %></span>
|
||||||
|
@ -59,8 +60,8 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
total_count={@presences_count}
|
total_count={@presences_count}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div id="dialogs" phx-update="append">
|
<div id="dialogs" phx-update="stream">
|
||||||
<%= for {_id, song} <- if(@owns_profile?, do: @songs, else: []), id = "delete-modal-#{song.id}" do %>
|
<%= for {_id, song} <- if(@owns_profile?, do: @streams.songs, else: []), id = "delete-modal-#{song.id}" do %>
|
||||||
<.modal
|
<.modal
|
||||||
id={id}
|
id={id}
|
||||||
on_confirm={
|
on_confirm={
|
||||||
|
@ -80,14 +81,18 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
|
|
||||||
<.table
|
<.table
|
||||||
id="songs"
|
id="songs"
|
||||||
rows={@songs}
|
rows={@streams.songs}
|
||||||
row_id={fn {id, _song} -> id end}
|
row_id={fn {id, _song} -> id end}
|
||||||
row_click={fn {_id, song} -> JS.push("play_or_pause", value: %{id: song.id}) end}
|
row_click={fn {_id, song} -> JS.push("play_or_pause", value: %{id: song.id}) end}
|
||||||
row_remove={fn {id, _song} -> hide("##{id}") end}
|
row_remove={fn {id, _song} -> hide("##{id}") end}
|
||||||
streamable
|
streamable
|
||||||
sortable_drop="row_dropped"
|
sortable_drop="row_dropped"
|
||||||
>
|
>
|
||||||
<:col :let={{_id, song}} label="Title" class!="px-6 py-3 text-sm font-medium text-gray-900 min-w-[20rem] cursor-pointer">
|
<:col
|
||||||
|
:let={{_id, song}}
|
||||||
|
label="Title"
|
||||||
|
class!="px-6 py-3 text-sm font-medium text-gray-900 md:min-w-[20rem] cursor-pointer"
|
||||||
|
>
|
||||||
<span :if={song.status == :playing} class="flex pt-1 relative mr-2 w-4">
|
<span :if={song.status == :playing} class="flex pt-1 relative mr-2 w-4">
|
||||||
<span class="w-3 h-3 animate-ping bg-purple-400 rounded-full absolute"></span>
|
<span class="w-3 h-3 animate-ping bg-purple-400 rounded-full absolute"></span>
|
||||||
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1" aria-label="Playing" role="button" />
|
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1" aria-label="Playing" role="button" />
|
||||||
|
@ -117,7 +122,7 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
label="Attribution"
|
label="Attribution"
|
||||||
class="max-w-5xl break-words text-gray-600 font-light"
|
class="max-w-5xl break-words text-gray-600 font-light"
|
||||||
>
|
>
|
||||||
<%= song.attribution %>
|
<%= song.position %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={{_id, song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %></:col>
|
<:col :let={{_id, song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %></:col>
|
||||||
<:col :let={{_id, song}} :if={@owns_profile?} label="">
|
<:col :let={{_id, song}} :if={@owns_profile?} label="">
|
||||||
|
@ -151,15 +156,18 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
song.id
|
song.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
songs = MediaLibrary.list_profile_songs(profile, 50)
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> assign(
|
|> assign(
|
||||||
active_song_id: active_song_id,
|
active_song_id: active_song_id,
|
||||||
active_profile_id: current_user.active_profile_user_id,
|
active_profile_id: current_user.active_profile_user_id,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
owns_profile?: MediaLibrary.owns_profile?(current_user, profile)
|
owns_profile?: MediaLibrary.owns_profile?(current_user, profile),
|
||||||
|
songs_count: Enum.count(songs)
|
||||||
)
|
)
|
||||||
|> stream_songs()
|
|> stream(:songs, songs)
|
||||||
|> assign_presences()
|
|> assign_presences()
|
||||||
|
|
||||||
{:ok, socket, temporary_assigns: [presences: %{}]}
|
{:ok, socket, temporary_assigns: [presences: %{}]}
|
||||||
|
@ -201,6 +209,7 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
def handle_event("row_dropped", %{"id" => dom_id, "old" => old_idx, "new" => new_idx}, socket) do
|
def handle_event("row_dropped", %{"id" => dom_id, "old" => old_idx, "new" => new_idx}, socket) do
|
||||||
"songs-" <> id = dom_id
|
"songs-" <> id = dom_id
|
||||||
song = MediaLibrary.get_song!(id)
|
song = MediaLibrary.get_song!(id)
|
||||||
|
|
||||||
if song.user_id == socket.assigns.current_user.id and song.position == old_idx do
|
if song.user_id == socket.assigns.current_user.id and song.position == old_idx do
|
||||||
:ok = MediaLibrary.update_song_position(song, new_idx)
|
:ok = MediaLibrary.update_song_position(song, new_idx)
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
|
@ -247,11 +256,19 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MediaLibrary, %MediaLibrary.Events.SongsImported{songs: songs}}, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.SongsImported{songs: songs}}, socket) do
|
||||||
{:noreply, Enum.reduce(songs, socket, fn song, acc -> stream_insert(acc, :songs, song) end)}
|
{:noreply,
|
||||||
|
Enum.reduce(songs, socket, fn song, acc ->
|
||||||
|
acc
|
||||||
|
|> update(:songs_count, &(&1 + 1))
|
||||||
|
|> stream_insert(:songs, song)
|
||||||
|
end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MediaLibrary, %MediaLibrary.Events.SongDeleted{song: song}}, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.SongDeleted{song: song}}, socket) do
|
||||||
{:noreply, stream_delete(socket, :songs, song)}
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> update(:songs_count, &(&1 - 1))
|
||||||
|
|> stream_delete(:songs, song)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({MediaLibrary, {:ping, ping}}, socket) do
|
def handle_info({MediaLibrary, {:ping, ping}}, socket) do
|
||||||
|
@ -338,10 +355,6 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp stream_songs(socket) do
|
|
||||||
stream(socket, :songs, MediaLibrary.list_profile_songs(socket.assigns.profile, 50))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp assign_presences(socket) do
|
defp assign_presences(socket) do
|
||||||
socket = assign(socket, presences_count: 0, presences: %{}, presence_ids: %{})
|
socket = assign(socket, presences_count: 0, presences: %{}, presence_ids: %{})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue