Current section

Files

Jump to
effusion lib effusion cli cli.ex
Raw

lib/effusion/cli/cli.ex

defmodule Effusion.CLI do
alias Effusion.BTP.Peer
alias Effusion.BTP.Pieces
alias Effusion.BTP.Request
alias Effusion.BTP.Torrent
alias Effusion.Format
alias Effusion.Statistics.Net, as: NetStats
alias Effusion.Statistics.Peer, as: PeerStats
alias Effusion.Statistics.PeerDownloadAverage
alias Effusion.Statistics.SessionDownloadAverage
alias Effusion.Repo
import Ecto.Query
use Timex
@moduledoc """
A command-line interface to Effusion.
"""
@aliases [o: :output]
@strict [output: :string]
@doc """
Download a file.
Usage: `effusion <name> -o <destination>`
"""
def main(argv \\ []) do
{_opts, [file], invalid} = OptionParser.parse(argv, strict: @strict, aliases: @aliases)
Enum.each(invalid, fn i ->
IO.warn("Invalid option #{i}")
end)
{:ok, metabin} = file |> Path.expand() |> File.read()
{:ok, meta} = Metatorrent.decode(metabin)
{:ok, info_hash} = Effusion.start_download(meta)
Process.sleep(100)
output_loop(info_hash)
end
@name_width 40
@progress_width 20
@percent_width 10
@downloaded_width 14
@duration_width 20
defp output_loop(
info_hash,
last_uploaded_bytes \\ 0,
last_timestamp \\ System.monotonic_time(:millisecond)
) do
torrent = Repo.one!(from torrent in Torrent,
where: torrent.info_hash == ^info_hash,
select: torrent)
dur = Timex.Interval.new(from: torrent.started, until: DateTime.utc_now()) |> Timex.Interval.duration(:duration)
downloaded = Pieces.bytes_completed(info_hash)
total_to_download = Pieces.torrent_length(info_hash)
fraction_downloaded = downloaded / total_to_download
name_formatted = torrent.name
percent_downloaded = Float.round(fraction_downloaded * 100, 3)
progress_bar = Format.progress_bar(percent_downloaded, @progress_width - 2)
downloaded_str = Format.bytes(downloaded)
duration_formatted = Timex.format_duration(dur)
this_loop_time = System.monotonic_time(:millisecond)
time_since_last_loop = max(this_loop_time - last_timestamp, 1)
downloaded_bytes = NetStats.recv_bytes()
uploaded_bytes = NetStats.sent_bytes()
dl_speed = SessionDownloadAverage.session_20sec_download_avg()
ul_speed = (uploaded_bytes - last_uploaded_bytes) / time_since_last_loop * 1_000
dl_speed_formatted = dl_speed |> trunc() |> Format.bytes()
ul_speed_formatted = ul_speed |> trunc() |> Format.bytes()
dl_bytes_formatted = downloaded_bytes |> Format.bytes()
ul_bytes_formatted = uploaded_bytes |> Format.bytes()
IO.write(IO.ANSI.clear())
IO.write(IO.ANSI.cursor(0, 0))
IO.puts(row("NAME", "PROGRESS", "PERCENT", "DOWNLOADED", "DURATION"))
IO.puts(
row(name_formatted, progress_bar, percent_downloaded, downloaded_str, duration_formatted)
)
IO.puts("")
IO.puts(
"Down: #{dl_speed_formatted}/s (#{dl_bytes_formatted}); Up: #{ul_speed_formatted}/s (#{
ul_bytes_formatted
})"
)
if NetStats.has_incoming_connections?() do
IO.puts("Receiving connections")
else
IO.puts("No incoming connections!")
end
IO.puts("Total TCP connections: #{PeerStats.num_tcp_peers()}")
IO.puts("Total half-open connections: #{PeerStats.num_peers_half_open()}")
IO.puts("Peers:")
peers_query = from peer in Peer,
left_join: request in Request,
on: peer.id == request.peer_id,
group_by: peer.id,
select: {peer, count(request)}
peers = Repo.all(peers_query)
Enum.each(peers, fn {peer, request_count} ->
if Peer.connected?(peer, info_hash) do
IO.puts "#{inspect peer.peer_id} -- #{PeerDownloadAverage.peer_20sec_download_avg(peer.peer_id) |> trunc() |> Format.bytes()}/s --- Requested #{request_count} blocks"
end
end)
Process.sleep(100)
output_loop(info_hash, uploaded_bytes, this_loop_time)
end
defp row(name, progress, percent, downloaded, duration) do
constrain_text(name, @name_width) <>
"| " <>
constrain_text(progress, @progress_width) <>
"| " <>
constrain_text(percent, @percent_width) <>
"| " <>
constrain_text(downloaded, @downloaded_width) <>
"| " <> constrain_text(duration, @duration_width)
end
defp constrain_text(string, length) do
string
|> to_string()
|> String.slice(0, length)
|> String.pad_trailing(length)
end
end