logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

attachments_cleanup_worker.ex (3279B)


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