diff --git a/zx b/zx index 6f83b5b..9b711fa 100755 --- a/zx +++ b/zx @@ -31,7 +31,7 @@ -type state() :: #s{}. --type serial() :: pos_integer(). +-type serial() :: integer(). -type package_id() :: {realm(), name(), version()}. -type package() :: {realm(), name()}. -type realm() :: lower0_9(). @@ -48,7 +48,7 @@ -type key_name() :: label(). -type lower0_9() :: [$a..$z | $0..$9 | $_]. -type label() :: [$a..$z | $0..$9 | $_ | $- | $.]. --type package_meta() :: #{}. +-type package_meta() :: map(). @@ -178,9 +178,10 @@ run(Identifier, Args) -> initialize(Type, PackageID) -> PackageString = package_string(PackageID), ok = log(info, "Initializing ~s...", [PackageString]), - Meta = [{package_id, PackageID}, - {deps, []}, - {type, Type}], + MetaList = [{package_id, PackageID}, + {deps, []}, + {type, Type}], + Meta = maps:from_list(MetaList), ok = write_meta(Meta), ok = log(info, "Project ~tp initialized.", [PackageString]), Message = @@ -312,7 +313,7 @@ ensure_installed(PackageID = {Realm, Name, Version}) -> Name :: name(), Version :: version(), Result :: exact - | {ok, version()} + | {ok, package_id()} | not_found. %% @private %% Fetch and install the latest compatible version of the given package ID, whether @@ -482,7 +483,7 @@ set_version(VersionString) -> update_version(Arg) -> Meta = read_meta(), - {package_id, PackageID} = lists:keyfind(package_id, 1, Meta), + PackageID = maps:get(package_id, Meta), update_version(Arg, PackageID, Meta). @@ -528,7 +529,7 @@ update_version(NewVersion, {Realm, Name, OldVersion}, OldMeta) -> update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> PackageID = {Realm, Name, NewVersion}, - NewMeta = lists:keystore(package_id, 1, OldMeta, {package_id, PackageID}), + NewMeta = maps:put(package_id, PackageID, OldMeta), ok = write_meta(NewMeta), ok = log(info, "Version changed from ~s to ~s.", @@ -546,11 +547,11 @@ update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> drop_dep(PackageID) -> PackageString = package_string(PackageID), Meta = read_meta(), - {deps, Deps} = lists:keyfind(deps, 1, Meta), + Deps = maps:get(deps, Meta), case lists:member(PackageID, Deps) of true -> NewDeps = lists:delete(PackageID, Deps), - NewMeta = lists:keystore(deps, 1, Meta, {deps, NewDeps}), + NewMeta = maps:put(deps, NewDeps, Meta), ok = write_meta(NewMeta), Message = "~ts removed from dependencies.", ok = log(info, Message, [PackageString]), @@ -660,7 +661,7 @@ execute(#s{type = lib, realm = Realm, name = Name, version = Version}, _) -> package(TargetDir) -> ok = log(info, "Packaging ~ts", [TargetDir]), Meta = read_meta(TargetDir), - {package_id, {Realm, _, _}} = lists:keyfind(package_id, 1, Meta), + {Realm, _, _} = maps:get(package_id, Meta), KeyDir = filename:join([zomp_dir(), "key", Realm]), ok = force_dir(KeyDir), Pattern = KeyDir ++ "/*.key.der", @@ -689,7 +690,7 @@ package(TargetDir) -> package(KeyID, TargetDir) -> Meta = read_meta(TargetDir), - {package_id, PackageID} = lists:keyfind(package_id, 1, Meta), + PackageID = maps:get(package_id, Meta), true = element(1, PackageID) == element(1, KeyID), PackageString = package_string(PackageID), ZrpFile = PackageString ++ ".zrp", @@ -710,7 +711,8 @@ package(KeyID, TargetDir) -> {ok, Key} = loadkey(private, KeyID), {ok, TgzBin} = file:read_file(TgzFile), Sig = public_key:sign(TgzBin, sha512, Key), - FinalMeta = [{modules, Modules}, {sig, {KeyID, Sig}} | Meta], + Add = fun({K, V}, M) -> maps:put(K, V, M) end, + FinalMeta = lists:foldl(Add, Meta, [{modules, Modules}, {sig, {KeyID, Sig}}]), ok = file:write_file("zomp.meta", term_to_binary(FinalMeta)), ok = erl_tar:create(ZrpFile, ["zomp.meta", TgzFile]), ok = file:delete(TgzFile), @@ -946,7 +948,7 @@ confirm_serial(Realm, Socket, Hosts) -> Socket; {ok, Current} when Current > Serial -> ok = log(info, "Node's serial newer than ours. Storing."), - NewSerials = lists:keystore(Realm, 1, Current, Serials), + NewSerials = lists:keystore(Realm, 1, Current, {Realm, Serials}), {ok, Host} = inet:peername(Socket), ok = write_terms(hosts_cache_file(Realm), [Host | Hosts]), ok = write_terms(SerialFile, NewSerials), @@ -1402,6 +1404,12 @@ dialyze() -> %%% Create Realm & Sysop +-spec create_realm() -> no_return(). +%% @private +%% Prompt the user to input the information necessary to create a new zomp realm, +%% package the data appropriately for the server and deliver the final keys and +%% realm file to the user. + create_realm() -> ConfFile = filename:join(zomp_dir(), "zomp.conf"), case file:consult(ConfFile) of @@ -1409,6 +1417,10 @@ create_realm() -> {error, enoent} -> create_realm([]) end. + +-spec create_realm(ZompConf) -> no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}]. + create_realm(ZompConf) -> Instructions = "~n" @@ -1432,6 +1444,11 @@ create_realm(ZompConf) -> create_realm(ZompConf) end. + +-spec create_realm(ZompConf, Realm) -> no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(). + create_realm(ZompConf, Realm) -> ExAddress = case lists:keyfind(external_address, 1, ZompConf) of @@ -1441,6 +1458,10 @@ create_realm(ZompConf, Realm) -> end, create_realm(ZompConf, Realm, ExAddress). + +-spec prompt_external_address() -> Result + when Result :: inet:hostname() | inet:ip_address(). + prompt_external_address() -> Message = external_address_prompt(), ok = io:format(Message), @@ -1452,6 +1473,11 @@ prompt_external_address() -> parse_address(String) end. + +-spec prompt_external_address(Current) -> Result + when Current :: inet:hostname() | inet:ip_address(), + Result :: inet:hostname() | inet:ip_address(). + prompt_external_address(Current) -> XAString = case inet:ntoa(Current) of @@ -1467,6 +1493,9 @@ prompt_external_address(Current) -> String -> parse_address(String) end. + +-spec external_address_prompt() -> string(). + external_address_prompt() -> "~n" " Enter a static, valid hostname or IPv4 or IPv6 address at which this host " @@ -1474,12 +1503,21 @@ external_address_prompt() -> "need to be reached from the internet).~n" " DO NOT INCLUDE A PORT NUMBER IN THIS STEP~n". + +-spec parse_address(string()) -> inet:hostname() | inet:ip_address(). + parse_address(String) -> case inet:parse_address(String) of {ok, Address} -> Address; {error, einval} -> String end. + +-spec create_realm(ZompConf, Realm, ExAddress) -> no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(), + ExAddress :: inet:hostname() | inet:ip_address(). + create_realm(ZompConf, Realm, ExAddress) -> Current = case lists:keyfind(external_port, 1, ZompConf) of @@ -1496,6 +1534,13 @@ create_realm(ZompConf, Realm, ExAddress) -> ExPort = prompt_port_number(Current), create_realm(ZompConf, Realm, ExAddress, ExPort). + +-spec create_realm(ZompConf, Realm, ExAddress, ExPort) -> no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(), + ExAddress :: inet:hostname() | inet:ip_address(), + ExPort :: inet:port_number(). + create_realm(ZompConf, Realm, ExAddress, ExPort) -> Current = case lists:keyfind(internal_port, 1, ZompConf) of @@ -1512,6 +1557,11 @@ create_realm(ZompConf, Realm, ExAddress, ExPort) -> InPort = prompt_port_number(Current), create_realm(ZompConf, Realm, ExAddress, ExPort, InPort). + +-spec prompt_port_number(Current) -> Result + when Current :: inet:port_number(), + Result :: inet:port_number(). + prompt_port_number(Current) -> Instructions = " A valid port is any number from 1 to 65535." @@ -1536,6 +1586,14 @@ prompt_port_number(Current) -> end end. + +-spec create_realm(ZompConf, Realm, ExAddress, ExPort, InPort) -> no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(), + ExAddress :: inet:hostname() | inet:ip_address(), + ExPort :: inet:port_number(), + InPort :: inet:port_number(). + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort) -> Instructions = "~n" @@ -1552,6 +1610,15 @@ create_realm(ZompConf, Realm, ExAddress, ExPort, InPort) -> create_realm(ZompConf, Realm, ExAddress, ExPort, InPort) end. + +-spec create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName) -> no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(), + ExAddress :: inet:hostname() | inet:ip_address(), + ExPort :: inet:port_number(), + InPort :: inet:port_number(), + UserName :: string(). + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName) -> Instructions = "~n" @@ -1579,6 +1646,17 @@ create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName) -> create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName) end. + +-spec create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> + no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(), + ExAddress :: inet:hostname() | inet:ip_address(), + ExPort :: inet:port_number(), + InPort :: inet:port_number(), + UserName :: string(), + Email :: string(). + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> Instructions = "~n" @@ -1671,6 +1749,8 @@ create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> halt(0). +-spec create_sysop() -> no_return(). + create_sysop() -> ok = log(info, "Fo' realz, yo! We be sysoppin up in hurr!"), halt(0). @@ -1749,9 +1829,9 @@ verify(Data, Signature, PubKey) -> %% Download a package to the local cache. fetch(Socket, PackageID) -> - ok = request_zrp(Socket, PackageID), - ok = receive_zrp(Socket, PackageID), - log(info, "Fetched ~ts", [package_string(PackageID)]). + {ok, LatestID} = request_zrp(Socket, PackageID), + ok = receive_zrp(Socket, LatestID), + log(info, "Fetched ~ts", [package_string(LatestID)]). request_zrp(Socket, PackageID) -> @@ -2183,33 +2263,6 @@ hurr() -> io:format("That isn't an option.~n"). %%% Directory & File Management -%-spec move_file(From, To) -> Result -% when From :: file:filename(), -% To :: file:filename(), -% Result :: ok -% | {error, Reason}, -% Reason :: bad_source -% | destination_exists -% | file:posix(). -%%% @private -%%% Utility function to safely copy a file From one path To another without clobbering the -%%% destination in the event it already exists. Both the source and the destination must be -%%% complete filenames, not directories. -% -%move_file(From, To) -> -% case {filelib:is_regular(From), filelib:is_file(To)} of -% {false, false} -> -% case file:copy(From, To) of -% {ok, _} -> file:delete(From); -% Error -> Error -% end; -% {true, _} -> -% {error, bad_source}; -% {false, true} -> -% {error, destination_exists} -% end. - - -spec ensure_zomp_home() -> ok. %% @private %% Ensure the zomp home directory exists and is populated.