diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/pleroma/media.ex | 6 | ||||
-rw-r--r-- | lib/pleroma/object.ex | 31 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 17 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub_controller.ex | 9 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/builder.ex | 4 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 13 | ||||
-rw-r--r-- | lib/pleroma/web/common_api.ex | 3 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/media_controller.ex | 23 | ||||
-rw-r--r-- | lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex | 9 | ||||
-rw-r--r-- | lib/pleroma/workers/attachments_cleanup_worker.ex | 97 |
10 files changed, 85 insertions, 127 deletions
diff --git a/lib/pleroma/media.ex b/lib/pleroma/media.ex index 431c06bb5..b9b001366 100644 --- a/lib/pleroma/media.ex +++ b/lib/pleroma/media.ex @@ -20,6 +20,8 @@ defmodule Pleroma.Media do field(:blurhash, :string) field(:meta, :map) + field(:removable, :boolean, virtual: true, default: false) + belongs_to(:object, Pleroma.Object) belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType) @@ -27,7 +29,7 @@ defmodule Pleroma.Media do end def create_from_object_data(%{"url" => [url]} = data, %{user: user} = opts) do - object_id = get_in(opts, [:object, "id"]) + object_id = get_in(opts, [:object, "id"]) || Map.get(opts, :object_id) %Media{} |> changeset(%{ @@ -48,7 +50,7 @@ defmodule Pleroma.Media do @spec authorize_access(Media.t(), User.t()) :: :ok | {:error, :forbidden} def authorize_access(%Media{user_id: user_id}, %User{id: user_id}), do: :ok - def authorize_access(%Media{user_id: user_id}, %User{id: user_id}), do: {:error, :forbidden} + def authorize_access(_media, _user), do: {:error, :forbidden} def update(%Media{} = media, attrs \\ %{}) do media diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index aaf123840..a794b13c2 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Object do alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.Media alias Pleroma.Object alias Pleroma.Object.Fetcher alias Pleroma.ObjectTombstone @@ -51,6 +52,7 @@ defmodule Pleroma.Object do def create(data) do Object.change(%Object{}, %{data: data}) |> Repo.insert() + |> maybe_handle_attachments() end def change(struct, params \\ %{}) do @@ -349,4 +351,33 @@ defmodule Pleroma.Object do def self_replies(object, opts \\ []), do: replies(object, Keyword.put(opts, :self_only, true)) + + defp maybe_handle_attachments( + {:ok, + %Object{id: object_id, data: %{"attachment" => [_ | _] = attachments} = data} = object} = + result + ) do + Enum.each(attachments, fn attachment -> + case attachment["id"] do + # New media incoming + nil -> + Media.create_from_object_data(attachment, %{ + user: User.get_by_ap_id(data["actor"]), + object_id: object_id + }) + + # Media pre-uploaded for a post + media_id -> + media_id + |> Media.get_by_id() + |> Media.update(%{object_id: object_id}) + end + + object + end) + + result + end + + defp maybe_handle_attachments(result), do: result end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c2948fd52..4322fc729 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -123,7 +123,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, {:containment, :ok} <- {:containment, Containment.contain_child(map)}, {:ok, map, object} <- insert_full_object(map), - :ok <- maybe_update_media(object), {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do # Splice in the child object if we have one. activity = Maps.put_if_present(activity, :object, object) @@ -166,18 +165,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - defp maybe_update_media(%Object{data: %{"attachment" => []}}), do: :ok - - defp maybe_update_media(%Object{data: %{"id" => id, "attachment" => attachments}}) do - Enum.each(attachments, fn data -> - with %{"id" => media_id} <- data do - media_id - |> Pleroma.Media.get_by_id() - |> Pleroma.Media.update(%{object_id: id}) - end - end) - end - defp insert_activity_with_expiration(data, local, recipients) do struct = %Activity{ data: data, @@ -1214,8 +1201,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do with {:ok, data} <- Upload.store(file, opts), - %User{} <- opts[:user] do - Pleroma.Media.create_from_object_data(data, %{user: opts[:user]}) + %User{} = user <- opts[:user] do + Pleroma.Media.create_from_object_data(data, %{user: user}) end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 2e5069cb5..ecb632b75 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Activity alias Pleroma.Delivery + alias Pleroma.Media alias Pleroma.Object alias Pleroma.Object.Fetcher alias Pleroma.User @@ -537,17 +538,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do - with {:ok, object} <- + with {:ok, media} <- ActivityPub.upload( file, - actor: User.ap_id(user), + user: user, description: Map.get(data, "description") ) do - Logger.debug(inspect(object)) + Logger.debug(inspect(media)) conn |> put_status(:created) - |> json(object.data) + |> json(Media.to_object_form(media)) end end end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index f56bfc600..abd67ebc2 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do """ alias Pleroma.Emoji + alias Pleroma.Media alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay @@ -137,6 +138,9 @@ defmodule Pleroma.Web.ActivityPub.Builder do } case opts[:attachment] do + %Media{} = media -> + {:ok, Map.put(basic, "attachment", Media.to_object_form(media)), []} + %Object{data: attachment_data} -> { :ok, diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 7f7387c31..af9143324 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Maps - alias Pleroma.Media alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Repo @@ -37,7 +36,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_actor() |> fix_url() |> fix_attachments() - |> fix_media() |> fix_context() |> fix_in_reply_to(options) |> fix_emoji() @@ -272,17 +270,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_attachments(object), do: object - def fix_media(%{"attachment" => [_ | _] = attachments} = object) do - Enum.each( - attachments, - &Media.create_from_object_data(&1, %{user: User.get_by_ap_id(object.actor), object: object}) - ) - - object - end - - def fix_media(object), do: object - def fix_url(%{"url" => url} = object) when is_map(url) do Map.put(object, "url", url["href"]) end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index b003e30c7..0e17c7a36 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity alias Pleroma.Conversation.Participation alias Pleroma.Formatter + alias Pleroma.Media alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -31,7 +32,7 @@ defmodule Pleroma.Web.CommonAPI do end def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do - with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), + with maybe_attachment <- opts[:media_id] && Media.get_by_id(opts[:media_id]), :ok <- validate_chat_content_length(content, !!maybe_attachment), {_, {:ok, chat_message_data, _meta}} <- {:build_object, diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 1bd521460..d9e5241da 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -36,34 +36,17 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def create(_conn, _data), do: {:error, :bad_request} - def _create(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do - with {:ok, object} <- - ActivityPub.upload( - file, - actor: User.ap_id(user), - description: Map.get(data, :description) - ) do - attachment_data = Map.put(object.data, "id", object.id) - - render(conn, "attachment.json", %{attachment: attachment_data}) - end - end - - def _create(_conn, _data), do: {:error, :bad_request} - @doc "POST /api/v2/media" def create2(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do - with {:ok, object} <- + with {:ok, media} <- ActivityPub.upload( file, - actor: User.ap_id(user), + user: user, description: Map.get(data, :description) ) do - attachment_data = Map.put(object.data, "id", object.id) - conn |> put_status(202) - |> render("attachment.json", %{attachment: attachment_data}) + |> render("media.json", %{media: media}) end end diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index 429ef5112..4f2aa83e1 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -24,8 +24,8 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do @doc "PUT /api/v1/pleroma/mascot" def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do with {:content_type, "image" <> _} <- {:content_type, file.content_type}, - {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do - attachment = render_attachment(object) + {:ok, media} <- ActivityPub.upload(file, user: user) do + attachment = Pleroma.Web.MastodonAPI.StatusView.render("media.json", %{media: media}) {:ok, _user} = User.mascot_update(user, attachment) json(conn, attachment) @@ -34,9 +34,4 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do render_error(conn, :unsupported_media_type, "mascots can only be images") end end - - defp render_attachment(object) do - attachment_data = Map.put(object.data, "id", object.id) - Pleroma.Web.MastodonAPI.StatusView.render("attachment.json", %{attachment: attachment_data}) - end end diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index a2373ebb9..bd3e3c03b 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do import Ecto.Query - alias Pleroma.Object + alias Pleroma.Media alias Pleroma.Repo use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup" @@ -14,22 +14,21 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do def perform(%Job{ args: %{ "op" => "cleanup_attachments", - "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}} + "object" => %{"data" => %{"attachment" => [_ | _] = attachments}} } }) do attachments - |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end) - |> fetch_objects - |> prepare_objects(actor, Enum.map(attachments, & &1["name"])) - |> filter_objects - |> do_clean + |> Enum.map(& &1["id"]) + |> get_media() + |> set_removable() + |> do_clean() {:ok, :success} end def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip} - defp do_clean({object_ids, attachment_urls}) do + defp do_clean(medias) do uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) base_url = @@ -38,70 +37,38 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do "/" ) - Enum.each(attachment_urls, fn href -> - href - |> String.trim_leading("#{base_url}") - |> uploader.delete_file() - end) + Enum.each(medias, fn media -> + with true <- media.removable do + media.href + |> String.trim_leading("#{base_url}") + |> uploader.delete_file() + end - delete_objects(object_ids) + Repo.delete(media) + end) end - defp delete_objects([_ | _] = object_ids) do - Repo.delete_all(from(o in Object, where: o.id in ^object_ids)) + defp get_media(ids) do + from(m in Media, + where: m.id in ^ids + ) + |> Repo.all() end - defp delete_objects(_), do: :ok + defp set_removable(medias) do + Enum.map(medias, fn media -> + from(m in Media, + where: m.href == ^media.href, + select: count(m.id) + ) + |> Repo.one!() + |> case do + 1 -> + %Media{media | removable: true} - # we should delete 1 object for any given attachment, but don't delete - # files if there are more than 1 object for it - defp filter_objects(objects) do - Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} -> - with 1 <- count do - {ids ++ [id], hrefs ++ [href]} - else - _ -> {ids ++ [id], hrefs} + _ -> + %Media{media | removable: false} end end) end - - defp prepare_objects(objects, actor, names) do - objects - |> Enum.reduce(%{}, fn %{ - id: id, - data: %{ - "url" => [%{"href" => href}], - "actor" => obj_actor, - "name" => name - } - }, - acc -> - Map.update(acc, href, %{id: id, count: 1}, fn val -> - case obj_actor == actor and name in names do - true -> - # set id of the actor's object that will be deleted - %{val | id: id, count: val.count + 1} - - false -> - # another actor's object, just increase count to not delete file - %{val | count: val.count + 1} - end - end) - end) - end - - defp fetch_objects(hrefs) do - from(o in Object, - where: - fragment( - "to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)", - o.data, - o.data, - ^hrefs - ) - ) - # The query above can be time consumptive on large instances until we - # refactor how uploads are stored - |> Repo.all(timeout: :infinity) - end end |