Fix file comparison (#2709)

This commit is contained in:
Jonatan Kłosko 2024-07-15 19:04:50 +02:00 committed by GitHub
parent e934b1763e
commit 0b69625987
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 52 additions and 15 deletions

View file

@ -77,6 +77,18 @@ defmodule Livebook.FileSystem.File do
{file.file_system_id, node, file.path} {file.file_system_id, node, file.path}
end end
@doc """
Checks if two files are equal.
Comparing files with `Kernel.==/2` may result in false-negatives,
because the structs hold additional information.
"""
@spec equal?(t(), t()) :: boolean()
def equal?(file1, file2) do
file1.path == file2.path and file1.file_system_id == file2.file_system_id and
file1.file_system_module == file2.file_system_module
end
@doc """ @doc """
Checks if the given file is within a file system local to its node. Checks if the given file is within a file system local to its node.
""" """

View file

@ -139,7 +139,7 @@ defmodule Livebook.NotebookManager do
@impl true @impl true
def handle_cast({:add_recent_notebook, file, name}, state = prev_state) do def handle_cast({:add_recent_notebook, file, name}, state = prev_state) do
recent_notebooks = Enum.reject(state.recent_notebooks, &(&1.file == file)) recent_notebooks = Enum.reject(state.recent_notebooks, &FileSystem.File.equal?(&1.file, file))
recent_notebooks = [ recent_notebooks = [
%{file: file, name: name, added_at: DateTime.utc_now()} | recent_notebooks %{file: file, name: name, added_at: DateTime.utc_now()} | recent_notebooks
@ -158,7 +158,7 @@ defmodule Livebook.NotebookManager do
end end
def handle_cast({:add_starred_notebook, file, name}, state = prev_state) do def handle_cast({:add_starred_notebook, file, name}, state = prev_state) do
if Enum.any?(state.starred_notebooks, &(&1.file == file)) do if Enum.any?(state.starred_notebooks, &FileSystem.File.equal?(&1.file, file)) do
{:noreply, state} {:noreply, state}
else else
starred_notebooks = [ starred_notebooks = [
@ -172,14 +172,16 @@ defmodule Livebook.NotebookManager do
end end
def handle_cast({:remove_recent_notebook, file}, state = prev_state) do def handle_cast({:remove_recent_notebook, file}, state = prev_state) do
recent_notebooks = Enum.reject(state.recent_notebooks, &(&1.file == file)) recent_notebooks = Enum.reject(state.recent_notebooks, &FileSystem.File.equal?(&1.file, file))
state = %{state | recent_notebooks: recent_notebooks} state = %{state | recent_notebooks: recent_notebooks}
broadcast_changes(state, prev_state) broadcast_changes(state, prev_state)
{:noreply, state, {:continue, :dump_state}} {:noreply, state, {:continue, :dump_state}}
end end
def handle_cast({:remove_starred_notebook, file}, state = prev_state) do def handle_cast({:remove_starred_notebook, file}, state = prev_state) do
starred_notebooks = Enum.reject(state.starred_notebooks, &(&1.file == file)) starred_notebooks =
Enum.reject(state.starred_notebooks, &FileSystem.File.equal?(&1.file, file))
state = %{state | starred_notebooks: starred_notebooks} state = %{state | starred_notebooks: starred_notebooks}
broadcast_changes(state, prev_state) broadcast_changes(state, prev_state)
{:noreply, state, {:continue, :dump_state}} {:noreply, state, {:continue, :dump_state}}

View file

@ -689,11 +689,15 @@ defmodule LivebookWeb.FileSelectComponent do
unhighlighted: name, unhighlighted: name,
file: file, file: file,
is_dir: FileSystem.File.dir?(file), is_dir: FileSystem.File.dir?(file),
is_running: file in running_files, is_running: running?(file, running_files),
editable: Keyword.get(opts, :editable, true) editable: Keyword.get(opts, :editable, true)
} }
end end
defp running?(file, running_files) do
Enum.any?(running_files, &FileSystem.File.equal?(&1, file))
end
defp hidden?(filename) do defp hidden?(filename) do
String.starts_with?(filename, ".") String.starts_with?(filename, ".")
end end

View file

@ -1,6 +1,8 @@
defmodule LivebookWeb.NotebookCardsComponent do defmodule LivebookWeb.NotebookCardsComponent do
use LivebookWeb, :live_component use LivebookWeb, :live_component
alias Livebook.FileSystem
@impl true @impl true
def render(assigns) do def render(assigns) do
assigns = assign_new(assigns, :card_icon, fn -> nil end) assigns = assign_new(assigns, :card_icon, fn -> nil end)
@ -72,10 +74,10 @@ defmodule LivebookWeb.NotebookCardsComponent do
end end
defp file_running?(file, sessions) do defp file_running?(file, sessions) do
Enum.any?(sessions, &(&1.file == file)) Enum.any?(sessions, &(&1.file && FileSystem.File.equal?(&1.file, file)))
end end
defp session_by_file(file, sessions) do defp session_by_file(file, sessions) do
Enum.find(sessions, &(&1.file == file)) Enum.find(sessions, &(&1.file && FileSystem.File.equal?(&1.file, file)))
end end
end end

View file

@ -276,11 +276,11 @@ defmodule LivebookWeb.OpenLive do
end end
defp file_running?(file, sessions) do defp file_running?(file, sessions) do
Enum.any?(sessions, &(&1.file == file)) Enum.any?(sessions, &(&1.file && FileSystem.File.equal?(&1.file, file)))
end end
defp session_id_by_file(file, sessions) do defp session_id_by_file(file, sessions) do
session = Enum.find(sessions, &(&1.file == file)) session = Enum.find(sessions, &(&1.file && FileSystem.File.equal?(&1.file, file)))
session.id session.id
end end
end end

View file

@ -112,14 +112,14 @@ defmodule LivebookWeb.OpenLive.FileComponent do
end end
defp file_running?(file, sessions) do defp file_running?(file, sessions) do
Enum.any?(sessions, &(&1.file == file)) Enum.any?(sessions, &(&1.file && FileSystem.File.equal?(&1.file, file)))
end end
defp files(sessions) do defp files(sessions) do
Enum.map(sessions, & &1.file) for session <- sessions, session.file, do: session.file
end end
defp session_by_file(file, sessions) do defp session_by_file(file, sessions) do
Enum.find(sessions, &(&1.file == file)) Enum.find(sessions, &(&1.file && FileSystem.File.equal?(&1.file, file)))
end end
end end

View file

@ -6,7 +6,7 @@ defmodule LivebookWeb.SessionLive.PersistenceComponent do
@impl true @impl true
def mount(socket) do def mount(socket) do
sessions = Sessions.list_sessions() sessions = Sessions.list_sessions()
running_files = Enum.map(sessions, & &1.file) running_files = for session <- sessions, session.file, do: session.file
{:ok, assign(socket, running_files: running_files)} {:ok, assign(socket, running_files: running_files)}
end end
@ -212,7 +212,9 @@ defmodule LivebookWeb.SessionLive.PersistenceComponent do
defp savable?(draft_file, saved_file, running_files) do defp savable?(draft_file, saved_file, running_files) do
file = normalize_file(draft_file) file = normalize_file(draft_file)
not FileSystem.File.dir?(draft_file) and (file not in running_files or file == saved_file) running? = Enum.any?(running_files, &FileSystem.File.equal?(&1, file))
changed? = saved_file == nil or not FileSystem.File.equal?(file, saved_file)
not FileSystem.File.dir?(draft_file) and (running? or changed?)
end end
defp map_diff(left, right) do defp map_diff(left, right) do

View file

@ -1377,7 +1377,7 @@ defmodule LivebookWeb.SessionLive.Render do
defp star_button(assigns) do defp star_button(assigns) do
~H""" ~H"""
<%= if @file in @starred_files do %> <%= if starred?(@file, @starred_files) do %>
<span class="tooltip left" data-tooltip="Unstar notebook"> <span class="tooltip left" data-tooltip="Unstar notebook">
<.icon_button phx-click="unstar_notebook"> <.icon_button phx-click="unstar_notebook">
<.remix_icon icon="star-fill" class="text-yellow-600" /> <.remix_icon icon="star-fill" class="text-yellow-600" />
@ -1392,4 +1392,8 @@ defmodule LivebookWeb.SessionLive.Render do
<% end %> <% end %>
""" """
end end
defp starred?(file, starred_files) do
Enum.any?(starred_files, &Livebook.FileSystem.File.equal?(&1, file))
end
end end

View file

@ -33,6 +33,17 @@ defmodule Livebook.FileSystem.FileTest do
end end
end end
describe "equal?/2" do
test "returns true for matching files built in different processes" do
file_system = FileSystem.Local.new()
file1 = Livebook.FileSystem.File.new(file_system, "/tmp/")
file2 = Task.await(Task.async(fn -> Livebook.FileSystem.File.local("/tmp/") end))
assert FileSystem.File.equal?(file1, file2)
end
end
describe "local/1" do describe "local/1" do
test "uses the globally configured local file system instance" do test "uses the globally configured local file system instance" do
assert FileSystem.File.local(p("/path")).file_system_id == assert FileSystem.File.local(p("/path")).file_system_id ==