Packages
nerves
1.14.1
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/nerves/utils/stream.ex
# SPDX-FileCopyrightText: 2016 Justin Schneck
# SPDX-FileCopyrightText: 2022 Frank Hunleth
# SPDX-FileCopyrightText: 2022 Jon Carstens
#
# SPDX-License-Identifier: Apache-2.0
#
defmodule Nerves.Utils.Stream do
@moduledoc false
use GenServer
@timer 10_000
@type options() :: [file: Path.t(), history_lines: non_neg_integer()]
@type state() :: %{
file: String.t() | nil,
timer: reference(),
history: :queue.queue(),
history_lines: non_neg_integer,
history_saved: non_neg_integer
}
@spec start_link(options()) :: GenServer.on_start()
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
@spec stop(GenServer.server()) :: :ok
def stop(pid) do
GenServer.stop(pid)
end
@spec history(GenServer.server()) :: String.t()
def history(pid) do
GenServer.call(pid, :history)
end
@impl GenServer
def init(opts) do
file = opts[:file]
history_lines = opts[:history_lines] || 100
_ = if file != nil, do: File.write(file, "", [:write])
{:ok,
%{
file: file,
timer: Process.send_after(self(), :keep_alive, @timer),
history: :queue.new(),
history_lines: history_lines,
history_saved: 0
}}
end
@impl GenServer
def handle_call(:history, _from, s) do
history =
s.history
|> :queue.to_list()
|> Enum.join()
{:reply, history, s}
end
@impl GenServer
def handle_info({:io_request, from, reply_as, {:put_chars, _encoding, chars}}, s) do
_ = if s.file != nil, do: File.write(s.file, chars, [:append])
s = save_history(s, chars)
reply(from, reply_as, :ok)
{:noreply, stdout(chars, s)}
end
@impl GenServer
def handle_info(:keep_alive, s) do
IO.write(".")
{:noreply, reset_timer(s)}
end
def handle_info(_, s) do
{:noreply, s}
end
@spec stdout(String.t(), state()) :: state()
def stdout(<<">>>", tail::binary>>, s), do: trim_write(">>>", "\n", tail, s)
def stdout(<<"\e[7m>>>", tail::binary>>, s), do: trim_write(">>>", "\e[7m", tail, s)
def stdout(_, s), do: s
defp trim_write(trim, split, bin, s) do
IO.write("\n")
[bin | _] =
bin
|> String.split(trim)
(trim <> bin)
|> String.split(split)
|> List.first()
|> String.trim()
|> IO.write()
reset_timer(s)
end
defp reset_timer(s) do
_ = Process.cancel_timer(s.timer)
%{s | timer: Process.send_after(self(), :keep_alive, @timer)}
end
defp save_history(%{history_saved: lines, history_lines: lines} = s, line) do
history = :queue.in(line, s.history)
{_, history} = :queue.out(history)
%{s | history: history}
end
defp save_history(s, line) do
history = :queue.in(line, s.history)
%{s | history: history, history_saved: s.history_saved + 1}
end
defp reply(from, reply_as, reply) do
send(from, {:io_reply, reply_as, reply})
end
end