Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
3.0KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Workers.AttachmentsCleanupWorker do
  5. import Ecto.Query
  6. alias Pleroma.Object
  7. alias Pleroma.Repo
  8. use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
  9. @impl Oban.Worker
  10. def perform(%Job{
  11. args: %{
  12. "op" => "cleanup_attachments",
  13. "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
  14. }
  15. }) do
  16. attachments
  17. |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
  18. |> fetch_objects
  19. |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
  20. |> filter_objects
  21. |> do_clean
  22. {:ok, :success}
  23. end
  24. def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
  25. defp do_clean({object_ids, attachment_urls}) do
  26. uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
  27. base_url =
  28. String.trim_trailing(
  29. Pleroma.Upload.base_url(),
  30. "/"
  31. )
  32. Enum.each(attachment_urls, fn href ->
  33. href
  34. |> String.trim_leading("#{base_url}")
  35. |> uploader.delete_file()
  36. end)
  37. delete_objects(object_ids)
  38. end
  39. defp delete_objects([_ | _] = object_ids) do
  40. Repo.delete_all(from(o in Object, where: o.id in ^object_ids))
  41. end
  42. defp delete_objects(_), do: :ok
  43. # we should delete 1 object for any given attachment, but don't delete
  44. # files if there are more than 1 object for it
  45. defp filter_objects(objects) do
  46. Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} ->
  47. with 1 <- count do
  48. {ids ++ [id], hrefs ++ [href]}
  49. else
  50. _ -> {ids ++ [id], hrefs}
  51. end
  52. end)
  53. end
  54. defp prepare_objects(objects, actor, names) do
  55. objects
  56. |> Enum.reduce(%{}, fn %{
  57. id: id,
  58. data: %{
  59. "url" => [%{"href" => href}],
  60. "actor" => obj_actor,
  61. "name" => name
  62. }
  63. },
  64. acc ->
  65. Map.update(acc, href, %{id: id, count: 1}, fn val ->
  66. case obj_actor == actor and name in names do
  67. true ->
  68. # set id of the actor's object that will be deleted
  69. %{val | id: id, count: val.count + 1}
  70. false ->
  71. # another actor's object, just increase count to not delete file
  72. %{val | count: val.count + 1}
  73. end
  74. end)
  75. end)
  76. end
  77. defp fetch_objects(hrefs) do
  78. from(o in Object,
  79. where:
  80. fragment(
  81. "to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
  82. o.data,
  83. o.data,
  84. ^hrefs
  85. )
  86. )
  87. # The query above can be time consumptive on large instances until we
  88. # refactor how uploads are stored
  89. |> Repo.all(timeout: :infinity)
  90. end
  91. end