Merge branch 'emoji-packs-create-dir' into 'develop'

When listing emoji packs, be sure to create the directory + add an endpoint to list remote packs since JS can't do that

See merge request pleroma/pleroma!1711
This commit is contained in:
rinpatch 2019-09-25 17:12:22 +00:00
commit 98be68e91b
4 changed files with 103 additions and 38 deletions

View File

@ -99,7 +99,7 @@ defmodule Pleroma.Emoji.Loader do
contents["files"]
|> Enum.map(fn {name, rel_file} ->
filename = Path.join("/emoji/#{pack_name}", rel_file)
{name, filename, pack_name}
{name, filename, ["pack:#{pack_name}"]}
end)
else
# Load from emoji.txt / all files

View File

@ -3,12 +3,33 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
require Logger
@emoji_dir_path Path.join(
def emoji_dir_path do
Path.join(
Pleroma.Config.get!([:instance, :static_dir]),
"emoji"
)
end
@cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
@doc """
Lists packs from the remote instance.
Since JS cannot ask remote instances for their packs due to CPS, it has to
be done by the server
"""
def list_from(conn, %{"instance_address" => address}) do
address = String.trim(address)
if shareable_packs_available(address) do
list_resp =
"#{address}/api/pleroma/emoji/packs" |> Tesla.get!() |> Map.get(:body) |> Jason.decode!()
json(conn, list_resp)
else
conn
|> put_status(:internal_server_error)
|> json(%{error: "The requested instance does not support sharing emoji packs"})
end
end
@doc """
Lists the packs available on the instance as JSON.
@ -17,7 +38,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
a map of "pack directory name" to pack.json contents.
"""
def list_packs(conn, _params) do
with {:ok, results} <- File.ls(@emoji_dir_path) do
# Create the directory first if it does not exist. This is probably the first request made
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_dir_path())},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_dir_path())} do
pack_infos =
results
|> Enum.filter(&has_pack_json?/1)
@ -28,24 +52,37 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|> Enum.into(%{})
json(conn, pack_infos)
else
{:create_dir, {:error, e}} ->
conn
|> put_status(:internal_server_error)
|> json(%{error: "Failed to create the emoji pack directory at #{emoji_dir_path()}: #{e}"})
{:ls, {:error, e}} ->
conn
|> put_status(:internal_server_error)
|> json(%{
error:
"Failed to get the contents of the emoji pack directory at #{emoji_dir_path()}: #{e}"
})
end
end
defp has_pack_json?(file) do
dir_path = Path.join(@emoji_dir_path, file)
dir_path = Path.join(emoji_dir_path(), file)
# Filter to only use the pack.json packs
File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json"))
end
defp load_pack(pack_name) do
pack_path = Path.join(@emoji_dir_path, pack_name)
pack_path = Path.join(emoji_dir_path(), pack_name)
pack_file = Path.join(pack_path, "pack.json")
{pack_name, Jason.decode!(File.read!(pack_file))}
end
defp validate_pack({name, pack}) do
pack_path = Path.join(@emoji_dir_path, name)
pack_path = Path.join(emoji_dir_path(), name)
if can_download?(pack, pack_path) do
archive_for_sha = make_archive(name, pack, pack_path)
@ -79,7 +116,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
{:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)])
cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files))
cache_seconds_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
cache_ms = :timer.seconds(cache_seconds_per_file * Enum.count(files))
Cachex.put!(
:emoji_packs_cache,
@ -115,7 +153,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
to download packs that the instance shares.
"""
def download_shared(conn, %{"name" => name}) do
pack_dir = Path.join(@emoji_dir_path, name)
pack_dir = Path.join(emoji_dir_path(), name)
pack_file = Path.join(pack_dir, "pack.json")
with {_, true} <- {:exists?, File.exists?(pack_file)},
@ -139,19 +177,12 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
end
end
@doc """
An admin endpoint to request downloading a pack named `pack_name` from the instance
`instance_address`.
If the requested instance's admin chose to share the pack, it will be downloaded
from that instance, otherwise it will be downloaded from the fallback source, if there is one.
"""
def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do
shareable_packs_available =
defp shareable_packs_available(address) do
"#{address}/.well-known/nodeinfo"
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
|> Map.get("links")
|> List.last()
|> Map.get("href")
# Get the actual nodeinfo address and fetch it
@ -160,8 +191,19 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|> Jason.decode!()
|> get_in(["metadata", "features"])
|> Enum.member?("shareable_emoji_packs")
end
if shareable_packs_available do
@doc """
An admin endpoint to request downloading a pack named `pack_name` from the instance
`instance_address`.
If the requested instance's admin chose to share the pack, it will be downloaded
from that instance, otherwise it will be downloaded from the fallback source, if there is one.
"""
def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do
address = String.trim(address)
if shareable_packs_available(address) do
full_pack =
"#{address}/api/pleroma/emoji/packs/list"
|> Tesla.get!()
@ -195,7 +237,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
%{body: emoji_archive} <- Tesla.get!(uri),
{_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do
local_name = data["as"] || name
pack_dir = Path.join(@emoji_dir_path, local_name)
pack_dir = Path.join(emoji_dir_path(), local_name)
File.mkdir_p!(pack_dir)
files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end)
@ -233,7 +275,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
Creates an empty pack named `name` which then can be updated via the admin UI.
"""
def create(conn, %{"name" => name}) do
pack_dir = Path.join(@emoji_dir_path, name)
pack_dir = Path.join(emoji_dir_path(), name)
if not File.exists?(pack_dir) do
File.mkdir_p!(pack_dir)
@ -257,7 +299,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
Deletes the pack `name` and all it's files.
"""
def delete(conn, %{"name" => name}) do
pack_dir = Path.join(@emoji_dir_path, name)
pack_dir = Path.join(emoji_dir_path(), name)
case File.rm_rf(pack_dir) do
{:ok, _} ->
@ -276,7 +318,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
`new_data` is the new metadata for the pack, that will replace the old metadata.
"""
def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do
pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"])
pack_file_p = Path.join([emoji_dir_path(), name, "pack.json"])
full_pack = Jason.decode!(File.read!(pack_file_p))
@ -360,7 +402,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
conn,
%{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params
) do
pack_dir = Path.join(@emoji_dir_path, pack_name)
pack_dir = Path.join(emoji_dir_path(), pack_name)
pack_file_p = Path.join(pack_dir, "pack.json")
full_pack = Jason.decode!(File.read!(pack_file_p))
@ -408,7 +450,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
"action" => "remove",
"shortcode" => shortcode
}) do
pack_dir = Path.join(@emoji_dir_path, pack_name)
pack_dir = Path.join(emoji_dir_path(), pack_name)
pack_file_p = Path.join(pack_dir, "pack.json")
full_pack = Jason.decode!(File.read!(pack_file_p))
@ -443,7 +485,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
conn,
%{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params
) do
pack_dir = Path.join(@emoji_dir_path, pack_name)
pack_dir = Path.join(emoji_dir_path(), pack_name)
pack_file_p = Path.join(pack_dir, "pack.json")
full_pack = Jason.decode!(File.read!(pack_file_p))
@ -513,11 +555,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
assumed to be emojis and stored in the new `pack.json` file.
"""
def import_from_fs(conn, _params) do
with {:ok, results} <- File.ls(@emoji_dir_path) do
with {:ok, results} <- File.ls(emoji_dir_path()) do
imported_pack_names =
results
|> Enum.filter(fn file ->
dir_path = Path.join(@emoji_dir_path, file)
dir_path = Path.join(emoji_dir_path(), file)
# Find the directories that do NOT have pack.json
File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json"))
end)
@ -533,7 +575,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
end
defp write_pack_json_contents(dir) do
dir_path = Path.join(@emoji_dir_path, dir)
dir_path = Path.join(emoji_dir_path(), dir)
emoji_txt_path = Path.join(dir_path, "emoji.txt")
files_for_pack = files_for_pack(emoji_txt_path, dir_path)

View File

@ -222,6 +222,7 @@ defmodule Pleroma.Web.Router do
put("/:name", EmojiAPIController, :create)
delete("/:name", EmojiAPIController, :delete)
post("/download_from", EmojiAPIController, :download_from)
post("/list_from", EmojiAPIController, :list_from)
end
scope "/packs" do

View File

@ -33,6 +33,28 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
refute pack["pack"]["can-download"]
end
test "listing remote packs" do
admin = insert(:user, info: %{is_admin: true})
conn = build_conn() |> assign(:user, admin)
resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})
%{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} ->
json(resp)
end)
assert conn
|> post(emoji_api_path(conn, :list_from), %{instance_address: "https://example.com"})
|> json_response(200) == resp
end
test "downloading a shared pack from download_shared" do
conn = build_conn()
@ -55,13 +77,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
mock(fn
%{method: :get, url: "https://old-instance/.well-known/nodeinfo"} ->
json([%{href: "https://old-instance/nodeinfo/2.1.json"}])
json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: []}})
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
json([%{href: "https://example.com/nodeinfo/2.1.json"}])
json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})