%% @doc Library module containing grisp_updater helper functions
-module(grisp_connect_updater).
%--- INCLUDES ------------------------------------------------------------------
-include_lib("kernel/include/logger.hrl").
%--- EXPORTS -------------------------------------------------------------------
-export([is_available/0]).
-export([system_info/0]).
-export([start_update/1]).
-export([validate/0]).
-export([cancel/0]).
% Disable dialyzer warnings because of unknown dependencies
-dialyzer({nowarn_function, system_info/0}).
-dialyzer({nowarn_function, start_update/1}).
-dialyzer({nowarn_function, validate/0}).
-dialyzer({nowarn_function, cancel/0}).
-dialyzer({nowarn_function, update_info/0}).
%--- API -----------------------------------------------------------------------
is_available() ->
is_running(grisp_updater).
system_info() ->
RelInfo = try release_handler:which_releases() of
[{RelName, RelVsn, _, _}] ->
#{relname => list_to_binary(RelName),
relvsn => list_to_binary(RelVsn)}
catch
exit:{noproc, _} ->
% Running in a shell
#{relname => null, relvsn => null};
error:undef ->
% Sasl is not running
#{relname => null, relvsn => null}
end,
UpdateInfo = update_info(),
maps:merge(RelInfo, UpdateInfo).
start_update(URL) ->
case grisp_connect_updater:is_available() of
true -> grisp_updater:start(URL,
grisp_connect_updater_progress,
#{client => self()}, #{});
false -> {error, grisp_updater_unavailable}
end.
validate() ->
case grisp_connect_updater:is_available() of
true -> grisp_updater:validate();
false -> {error, grisp_updater_unavailable}
end.
cancel() ->
case grisp_connect_updater:is_available() of
true -> grisp_updater:cancel();
false -> {error, grisp_updater_unavailable}
end.
%--- INTERNAL FUNCTIONS --------------------------------------------------------
is_running(AppName) ->
Apps = application:which_applications(),
case [App || {App, _Desc, _VSN} <- Apps, App =:= AppName] of
[] -> false;
[_] -> true
end.
update_info() ->
case grisp_connect_updater:is_available() of
false ->
#{update_enabled => false};
true ->
Info = grisp_updater:info(),
Status = grisp_updater:status(),
#{boot := Boot, valid := Valid, next := Next} = Info,
case {Status, Boot, Valid, Next} of
% Ready for update from removable media
{ready,
#{type := removable} = Boot,
#{type := system, id := ValidId},
#{type := system, id := NextId}}
when ValidId =:= NextId ->
#{update_enabled => true,
boot_source => Boot,
update_status => ready,
update_message => <<"Device ready for update">>};
% Ready for update from valid system
{ready,
#{type := system, id := BootId} = Boot,
#{type := system, id := ValidId},
#{type := system, id := NextId}}
when ValidId =:= NextId, ValidId =:= BootId ->
#{update_enabled => true,
boot_source => Boot,
update_status => ready,
update_message => <<"Device ready for update">>};
% Updating
{{updating, Stats}, Boot, _, _} ->
#{data_total := Total, data_checked := Checked,
data_skipped := Skipped, data_written := Written} = Stats,
Percent = (Checked + Skipped + Written) * 100 div (Total * 2),
#{update_enabled => true,
boot_source => Boot,
update_status => updating,
update_progress => Percent,
update_message => <<"Device is updating">>};
% Update Failed
{{error, canceled}, Boot, _, _} ->
#{update_enabled => true,
boot_source => Boot,
update_status => canceled,
update_message => <<"Device update canceled">>};
% Update Failed
{{error, _Reason}, Boot, _, _} ->
#{update_enabled => true,
boot_source => Boot,
update_status => failed,
update_message => <<"Device update failed">>};
% Update succeed
{{success, _Stats},
Boot,
#{type := system, id := ValidId},
#{type := system, id := NextId}}
when ValidId =/= NextId ->
#{update_enabled => true,
update_status => updated,
boot_source => Boot,
update_message => <<"Device updated, reboot required to validate the update">>,
action_required => reboot};
% Booted from removable after update
{ready,
#{type := removable} = Boot,
#{type := system, id := ValidId},
#{type := system, id := NextId}}
when ValidId =/= NextId ->
#{update_enabled => true,
boot_source => Boot,
update_status => updated,
update_message => <<"Device updated but the SD card wasn't removed before rebooting">>,
action_required => remove_sdcard_and_reboot};
% Updated and rebooted
{ready,
#{type := system, id := TestId} = Boot,
#{type := system, id := ValidId},
#{type := system, id := NextId}}
when ValidId =/= TestId, ValidId =:= NextId ->
#{update_enabled => true,
boot_source => Boot,
update_status => updated,
update_message => <<"Device updated, validation required">>,
action_required => validate};
_ ->
#{update_enabled => true}
end
end.