Packages

A Magnet URI encoder and decoder.

Current section

Files

Jump to
magnet lib magnet.ex
Raw

lib/magnet.ex

defmodule Magnet do
@moduledoc """
`Magnet` struct which represents Magnet URI.
See: https://en.wikipedia.org/wiki/Magnet_URI_scheme
"""
defstruct name: nil,
length: nil,
info_hash: [],
fallback: nil,
source: [],
keywords: [],
manifest: nil,
announce: [],
experimental: %{}
@type t :: %__MODULE__{
name: String.t(),
length: number,
info_hash: [String.t()],
fallback: String.t(),
source: [String.t()],
keywords: [String.t()],
manifest: String.t(),
announce: [String.t()],
experimental: map
}
defdelegate decode(data), to: Magnet.Decoder
defdelegate encode(data), to: Magnet.Encoder
end
defimpl Collectable, for: Magnet do
@spec into(Magnet.t()) ::
{Magnet.t(),
(Magnet.t(), {:cont, {String.t(), String.t()}} | :done | :halt -> Magnet.t() | :ok)}
def into(original) do
{original,
fn
# ignore entries with empty values
acc, {:cont, {_, ""}} ->
acc
# as (Acceptable Source) - Web link to the file online
acc, {:cont, {"as", value}} ->
uri = URI.decode(value)
%Magnet{acc | fallback: uri}
# dn (Display Name) - Suggested filename
acc, {:cont, {"dn", value}} ->
%Magnet{acc | name: value}
# kt (Keyword Topic) - Key words for the given torrent
acc, {:cont, {<<"kt", priority::binary>>, value}} ->
entry = parse_suffix_number(priority, String.split(value, "+"))
%Magnet{acc | keywords: [entry | acc.keywords]}
# mt (Manifest Topic) - link to a file that contains a list of magneto (MAGMA - MAGnet MAnifest)
acc, {:cont, {"mt", value}} ->
%Magnet{acc | manifest: value}
# tr (address TRacker) - Tracker/Announce URLs for BitTorrent downloads
acc, {:cont, {<<"tr", priority::binary>>, value}} ->
announce = URI.decode(value)
entry = parse_suffix_number(priority, announce)
%Magnet{acc | announce: [entry | acc.announce]}
# xl (eXact Length) - File size in bytes
acc, {:cont, {"xl", value}} ->
length = String.to_integer(value)
%Magnet{acc | length: length}
# xs (eXact Source) - peer-to-peer links
acc, {:cont, {<<"xs", priority::binary>>, value}} ->
uri = URI.decode(value)
entry = parse_suffix_number(priority, uri)
%Magnet{acc | source: [entry | acc.source]}
# xt (eXact Topic) - URN containing file hash
acc, {:cont, {<<"xt", priority::binary>>, value}} ->
entry = parse_suffix_number(priority, value)
%Magnet{acc | info_hash: [entry | acc.info_hash]}
acc, {:cont, {<<"x.", key::binary>>, value}} ->
experimental = URI.decode(value)
%Magnet{acc | experimental: Map.put(acc.experimental, key, experimental)}
acc, :done ->
keywords =
acc.keywords
|> sort_by_priority
|> List.flatten()
|> Enum.dedup()
%Magnet{
acc
| info_hash: prepare_list(acc.info_hash),
announce: prepare_list(acc.announce),
source: prepare_list(acc.source),
keywords: keywords
}
_, :halt ->
:ok
end}
end
@spec prepare_list([{number, String.t()}]) :: [String.t()]
defp prepare_list(list) do
list
|> sort_by_priority
|> Enum.dedup()
end
@spec sort_by_priority([{number, String.t()}]) :: [String.t()]
defp sort_by_priority(priority_list) do
priority_list
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map(&elem(&1, 1))
end
@spec parse_suffix_number(String.t(), any) :: {number, any}
defp parse_suffix_number("", value),
do: {0, value}
defp parse_suffix_number(<<".", number::binary>>, value) do
with {num, _} <- Integer.parse(number), do: {num, value}
end
end