Delete files when updating parent identities
Closes #127 Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -70,8 +70,8 @@ defmodule Mobilizon.Actors.Actor do
|
||||
many_to_many(:memberships, Actor, join_through: Member)
|
||||
belongs_to(:user, User)
|
||||
has_many(:feed_tokens, FeedToken, foreign_key: :actor_id)
|
||||
embeds_one(:avatar, File)
|
||||
embeds_one(:banner, File)
|
||||
embeds_one(:avatar, File, on_replace: :update)
|
||||
embeds_one(:banner, File, on_replace: :update)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@@ -9,6 +9,8 @@ defmodule Mobilizon.Actors do
|
||||
alias Mobilizon.Repo
|
||||
|
||||
alias Mobilizon.Actors.{Actor, Bot, Member, Follower}
|
||||
alias Mobilizon.Media.File
|
||||
alias Ecto.Multi
|
||||
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
require Logger
|
||||
@@ -119,9 +121,24 @@ defmodule Mobilizon.Actors do
|
||||
def update_actor(%Actor{} = actor, attrs) do
|
||||
actor
|
||||
|> Actor.changeset(attrs)
|
||||
|> delete_files_if_media_changed()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
defp delete_files_if_media_changed(%Ecto.Changeset{changes: changes} = changeset) do
|
||||
Enum.each([:avatar, :banner], fn key ->
|
||||
if Map.has_key?(changes, key) do
|
||||
with %Ecto.Changeset{changes: %{url: new_url}} <- changes[key],
|
||||
old_url <- Map.from_struct(changeset.data) |> Map.get(key) |> Map.get(:url),
|
||||
false <- new_url == old_url do
|
||||
MobilizonWeb.Upload.remove(old_url)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
changeset
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a Actor.
|
||||
|
||||
@@ -135,6 +152,26 @@ defmodule Mobilizon.Actors do
|
||||
|
||||
"""
|
||||
@spec delete_actor(Actor.t()) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
|
||||
def delete_actor(%Actor{domain: nil} = actor) do
|
||||
case Multi.new()
|
||||
|> Multi.delete(:actor, actor)
|
||||
|> Multi.run(:remove_banner, fn _repo,
|
||||
%{actor: %Actor{banner: %File{url: url}}} = _picture ->
|
||||
MobilizonWeb.Upload.remove(url)
|
||||
end)
|
||||
|> Multi.run(:remove_avatar, fn _repo,
|
||||
%{actor: %Actor{avatar: %File{url: url}}} = _picture ->
|
||||
MobilizonWeb.Upload.remove(url)
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
{:ok, %{actor: %Actor{} = actor}} ->
|
||||
{:ok, actor}
|
||||
|
||||
{:error, remove, error, _} when remove in [:remove_banner, :remove_avatar] ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_actor(%Actor{} = actor) do
|
||||
Repo.delete(actor)
|
||||
end
|
||||
|
||||
@@ -7,6 +7,8 @@ defmodule Mobilizon.Media do
|
||||
alias Mobilizon.Repo
|
||||
|
||||
alias Mobilizon.Media.Picture
|
||||
alias Mobilizon.Media.File
|
||||
alias Ecto.Multi
|
||||
|
||||
@doc false
|
||||
def data() do
|
||||
@@ -97,7 +99,15 @@ defmodule Mobilizon.Media do
|
||||
|
||||
"""
|
||||
def delete_picture(%Picture{} = picture) do
|
||||
Repo.delete(picture)
|
||||
case Multi.new()
|
||||
|> Multi.delete(:picture, picture)
|
||||
|> Multi.run(:remove, fn _repo, %{picture: %Picture{file: %File{url: url}}} = _picture ->
|
||||
MobilizonWeb.Upload.remove(url)
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
{:ok, %{picture: %Picture{} = picture}} -> {:ok, picture}
|
||||
{:error, :remove, error, _} -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -88,6 +88,16 @@ defmodule MobilizonWeb.Upload do
|
||||
end
|
||||
end
|
||||
|
||||
def remove(url, opts \\ []) do
|
||||
with opts <- get_opts(opts),
|
||||
%URI{path: "/media/" <> path, host: host} <- URI.parse(url),
|
||||
true <- host == MobilizonWeb.Endpoint.host() do
|
||||
MobilizonWeb.Uploaders.Uploader.remove_file(opts.uploader, path)
|
||||
else
|
||||
%URI{} = _uri -> {:error, "URL doesn't match pattern"}
|
||||
end
|
||||
end
|
||||
|
||||
def char_unescaped?(char) do
|
||||
URI.char_unreserved?(char) or char == ?/
|
||||
end
|
||||
|
||||
@@ -14,18 +14,8 @@ defmodule MobilizonWeb.Uploaders.Local do
|
||||
end
|
||||
|
||||
def put_file(upload) do
|
||||
{local_path, file} =
|
||||
case Enum.reverse(String.split(upload.path, "/", trim: true)) do
|
||||
[file] ->
|
||||
{upload_path(), file}
|
||||
|
||||
[file | folders] ->
|
||||
path = Path.join([upload_path()] ++ Enum.reverse(folders))
|
||||
File.mkdir_p!(path)
|
||||
{path, file}
|
||||
end
|
||||
|
||||
result_file = Path.join(local_path, file)
|
||||
{path, file} = local_path(upload.path)
|
||||
result_file = Path.join(path, file)
|
||||
|
||||
unless File.exists?(result_file) do
|
||||
File.cp!(upload.tempfile, result_file)
|
||||
@@ -34,6 +24,40 @@ defmodule MobilizonWeb.Uploaders.Local do
|
||||
:ok
|
||||
end
|
||||
|
||||
def remove_file(path) do
|
||||
with {path, file} <- local_path(path),
|
||||
full_path <- Path.join(path, file),
|
||||
true <- File.exists?(full_path),
|
||||
:ok <- File.rm(full_path),
|
||||
:ok <- remove_folder(path) do
|
||||
{:ok, path}
|
||||
else
|
||||
false -> {:error, "File #{path} doesn't exist"}
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_folder(path) do
|
||||
with {:subfolder, true} <- {:subfolder, path != upload_path()},
|
||||
{:empty_folder, {:ok, [] = _files}} <- {:empty_folder, File.ls(path)} do
|
||||
File.rmdir(path)
|
||||
else
|
||||
{:subfolder, _} -> :ok
|
||||
{:empty_folder, _} -> {:error, "Error: Folder is not empty"}
|
||||
end
|
||||
end
|
||||
|
||||
defp local_path(path) do
|
||||
case Enum.reverse(String.split(path, "/", trim: true)) do
|
||||
[file] ->
|
||||
{upload_path(), file}
|
||||
|
||||
[file | folders] ->
|
||||
path = Path.join([upload_path()] ++ Enum.reverse(folders))
|
||||
File.mkdir_p!(path)
|
||||
{path, file}
|
||||
end
|
||||
end
|
||||
|
||||
def upload_path do
|
||||
Mobilizon.CommonConfig.get!([__MODULE__, :uploads])
|
||||
end
|
||||
|
||||
@@ -35,6 +35,8 @@ defmodule MobilizonWeb.Uploaders.Uploader do
|
||||
@callback put_file(MobilizonWeb.Upload.t()) ::
|
||||
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
|
||||
|
||||
@callback remove_file(file_spec()) :: :ok | {:ok, file_spec()} | {:error, String.t()}
|
||||
|
||||
@callback http_callback(Plug.Conn.t(), Map.t()) ::
|
||||
{:ok, Plug.Conn.t()}
|
||||
| {:ok, Plug.Conn.t(), file_spec()}
|
||||
@@ -42,7 +44,6 @@ defmodule MobilizonWeb.Uploaders.Uploader do
|
||||
@optional_callbacks http_callback: 2
|
||||
|
||||
@spec put_file(module(), MobilizonWeb.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
|
||||
|
||||
def put_file(uploader, upload) do
|
||||
case uploader.put_file(upload) do
|
||||
:ok -> {:ok, {:file, upload.path}}
|
||||
@@ -52,6 +53,10 @@ defmodule MobilizonWeb.Uploaders.Uploader do
|
||||
end
|
||||
end
|
||||
|
||||
def remove_file(uploader, path) do
|
||||
uploader.remove_file(path)
|
||||
end
|
||||
|
||||
defp handle_callback(uploader, upload) do
|
||||
:global.register_name({__MODULE__, upload.path}, self())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user