First
[anni] / lib / mix / tasks / pleroma / uploads.ex
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
5 defmodule Mix.Tasks.Pleroma.Uploads do
6   use Mix.Task
7   import Mix.Pleroma
8   alias Pleroma.Upload
9   alias Pleroma.Uploaders.Local
10   require Logger
11
12   @log_every 50
13
14   @shortdoc "Migrates uploads from local to remote storage"
15   @moduledoc File.read!("docs/administration/CLI_tasks/uploads.md")
16
17   def run(["migrate_local", target_uploader | args]) do
18     delete? = Enum.member?(args, "--delete")
19     start_pleroma()
20     local_path = Pleroma.Config.get!([Local, :uploads])
21     uploader = Module.concat(Pleroma.Uploaders, target_uploader)
22
23     unless Code.ensure_loaded?(uploader) do
24       raise("The uploader #{inspect(uploader)} is not an existing/loaded module.")
25     end
26
27     target_enabled? = Pleroma.Config.get([Upload, :uploader]) == uploader
28
29     unless target_enabled? do
30       Pleroma.Config.put([Upload, :uploader], uploader)
31     end
32
33     shell_info("Migrating files from local #{local_path} to #{to_string(uploader)}")
34
35     if delete? do
36       shell_info(
37         "Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)"
38       )
39
40       :timer.sleep(:timer.seconds(5))
41     end
42
43     uploads =
44       File.ls!(local_path)
45       |> Enum.map(fn id ->
46         root_path = Path.join(local_path, id)
47
48         cond do
49           File.dir?(root_path) ->
50             files = for file <- File.ls!(root_path), do: {id, file, Path.join([root_path, file])}
51
52             case List.first(files) do
53               {id, file, path} ->
54                 {%Pleroma.Upload{id: id, name: file, path: id <> "/" <> file, tempfile: path},
55                  root_path}
56
57               _ ->
58                 nil
59             end
60
61           File.exists?(root_path) ->
62             file = Path.basename(id)
63             hash = Path.rootname(id)
64             {%Pleroma.Upload{id: hash, name: file, path: file, tempfile: root_path}, root_path}
65
66           true ->
67             nil
68         end
69       end)
70       |> Enum.filter(& &1)
71
72     total_count = length(uploads)
73     shell_info("Found #{total_count} uploads")
74
75     uploads
76     |> Task.async_stream(
77       fn {upload, root_path} ->
78         case Upload.store(upload, uploader: uploader, filters: [], size_limit: nil) do
79           {:ok, _} ->
80             if delete?, do: File.rm_rf!(root_path)
81             Logger.debug("uploaded: #{inspect(upload.path)} #{inspect(upload)}")
82             :ok
83
84           error ->
85             shell_error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
86         end
87       end,
88       timeout: 150_000
89     )
90     |> Stream.chunk_every(@log_every)
91     # credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation
92     |> Enum.reduce(0, fn done, count ->
93       count = count + length(done)
94       shell_info("Uploaded #{count}/#{total_count} files")
95       count
96     end)
97
98     shell_info("Done!")
99   end
100 end