src/grisp_connect_updater_progress.erl

-module(grisp_connect_updater_progress).

%--- Includes ------------------------------------------------------------------

-include_lib("kernel/include/logger.hrl").


%--- Types ---------------------------------------------------------------------


%--- Exports -------------------------------------------------------------------


% Behaviour grisp_updater_progress callbacks
-export([progress_init/1]).
-export([progress_update/2]).
-export([progress_warning/3]).
-export([progress_error/4]).
-export([progress_done/2]).


%--- records -------------------------------------------------------------------

-record(state, {
    client :: pid(),
    last_notification :: undefined | integer()
}).


%--- API Functions -------------------------------------------------------------


%--- Behavior grisp_updater_progress Callback ----------------------------------

progress_init(#{client := PID} = _Opts) ->
    {ok, #state{
        last_notification = erlang:system_time(millisecond),
        client = PID
    }}.

progress_update(#state{last_notification = LastLog} = State, Stats) ->
    case (erlang:system_time(millisecond) - LastLog) > 1000 of
        false ->
            {ok, State};
        true  ->
            UpdatePercentage = progress_percent(Stats),
            % Recheck log level when there is another way to check the progress update
            ?LOG_INFO("Update progress: ~b%", [UpdatePercentage]),
            grisp_connect_client:notify(
                <<"update">>,
                <<"software_update_event">>,
                #{event      => progress,
                  percentage => UpdatePercentage}),
            {ok, State#state{last_notification = erlang:system_time(millisecond)}}
    end.

progress_warning(State, Reason, Msg) ->
    ?LOG_WARNING("Update warning; ~s: ~p", [Msg, Reason]),
    grisp_connect_client:notify(
        <<"update">>,
        <<"software_update_event">>,
        #{event   => warning,
          reason  => as_json(Reason),
          message => as_json_string(Msg)}),
    {ok, State}.

progress_error(#state{}, Stats, Reason, Msg) ->
    UpdatePercentage = progress_percent(Stats),
    ?LOG_ERROR("Update failed after ~b% : ~p", [UpdatePercentage, Reason]),
    grisp_connect_client:notify(
        <<"update">>,
        <<"software_update_event">>,
        #{event      => error,
          reason     => as_json(Reason),
          message    => as_json_string(Msg),
          percentage => UpdatePercentage}),
    ok.

progress_done(#state{}, _Stats) ->
    ?LOG_NOTICE("Update done", []),
    grisp_connect_client:notify(
        <<"update">>,
        <<"software_update_event">>,
        #{event => done}),
    ok.


%--- Internal Functions --------------------------------------------------------

as_json_string(undefined) -> null;
as_json_string(Value) when is_binary(Value) -> Value;
as_json_string(Value) when is_atom(Value) -> Value;
as_json_string(Value) when is_integer(Value) -> integer_to_binary(Value);
as_json_string(Value) when is_float(Value) -> float_to_binary(Value);
as_json_string(Value) when is_list(Value) -> list_to_binary(Value).

as_json(undefined) -> null;
as_json(Value) when is_binary(Value) -> Value;
as_json(Value) when is_atom(Value) -> Value;
as_json(Value) when is_integer(Value) -> Value;
as_json(Value) when is_float(Value) -> Value;
as_json(Value) when is_list(Value) -> [as_json(V) || V <- Value];
as_json(Value) when is_map(Value) ->
    maps:from_list([{as_json_string(K), as_json(V)} || {K, V} <- maps:to_list(Value)]).


progress_percent(Stats) ->
    #{data_total := Total, data_checked := Checked,
      data_skipped := Skipped, data_written := Written} = Stats,
    (Checked + Skipped + Written) * 100 div (Total * 2).