diff --git a/zx b/zx index 64b87d5..f8c5246 100755 --- a/zx +++ b/zx @@ -87,10 +87,60 @@ start(["set", "dep", PackageString]) -> set_dep(PackageID); start(["set", "version", VersionString]) -> set_version(VersionString); +start(["list", "realms"]) -> + list_realms(); +start(["list", "packages", Realm]) -> + case valid_lower0_9(Realm) of + true -> + list_packages(Realm); + false -> + ok = log(error, "Bad realm name."), + halt(1) + end; +start(["list", "versions", Package]) -> + case string:lexemes(Package, "-") of + [Realm, Name] -> + list_versions({Realm, Name}); + _ -> + ok = log(error, "Bad package name."), + halt(1) + end; +start(["list", "pending", Package]) -> + case string:lexemes(Package, "-") of + [Realm, Name] -> + list_pending({Realm, Name}); + _ -> + ok = log(error, "Bad package name."), + halt(1) + end; +start(["list", "resigns", Realm]) -> + case valid_lower0_9(Realm) of + true -> + list_resigns(Realm); + false -> + ok = log(error, "Bad realm name."), + halt(1) + end; start(["add", "realm", RealmFile]) -> add_realm(RealmFile); start(["add", "package", PackageName]) -> add_package(PackageName); +start(["add", "packager", Package, UserName]) -> + add_packager(Package, UserName); +start(["add", "maintainer", Package, UserName]) -> + add_maintainer(Package, UserName); +start(["review", PackageString]) -> + PackageID = package_id(PackageString), + review(PackageID); +start(["approve", PackageString]) -> + PackageID = package_id(PackageString), + approve(PackageID); +start(["reject", PackageString]) -> + PackageID = package_id(PackageString), + reject(PackageID); +start(["resign", PackageString]) -> + PackageID = package_id(PackageString), + resign(PackageID); start(["drop", "dep", PackageString]) -> PackageID = package_id(PackageString), drop_dep(PackageID); @@ -548,6 +598,104 @@ update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> +%%% List Functions + +-spec list_realms() -> no_return(). +%% @private +%% List all currently configured realms. The definition of a "configured realm" is a +%% realm for which a .realm file exists in ~/.zomp/. The realms will be printed to +%% stdout and the program will exit. + +list_realms() -> + Pattern = filename:join(zomp_dir(), "*.realm"), + RealmFiles = filelib:wildcard(Pattern), + Realms = [filename:basename(RF, ".realm") || RF <- RealmFiles], + ok = lists:foreach(fun(R) -> io:format("~ts~n", [R]) end, Realms), + halt(0). + + +-spec list_packages(realm()) -> no_return(). +%% @private +%% Contact the indicated realm and query it for a list of registered packages and print +%% them to stdout. + +list_packages(Realm) -> + Socket = connect_user(Realm), + ok = send(Socket, {list, Realm}), + case recv_or_die(Socket) of + {ok, []} -> + ok = log(info, "Realm ~tp has no packages available.", [Realm]), + halt(0); + {ok, Packages} -> + Print = fun({R, N}) -> io:format("~ts-~ts~n", [R, N]) end, + ok = lists:foreach(Print, Packages), + halt(0); + end. + + +-spec list_versions(package()) -> no_return(). + +list_versions(Package = {Realm, Name}) -> + ok = valid_package(Package), + Socket = connect_user(Realm), + ok = send(Socket, {list, Realm, Name}), + case recv_or_die(Socket) of + {ok, []} -> + Message = "Package ~ts-~ts has no versions available.", + ok = log(info, Message, [Realm, Name]), + halt(0); + {ok, Versions} -> + Print = + fun(Version) -> + PackageString = package_string({Realm, Name, Version}), + io:format("~ts~n", [PackageString]) + end, + ok = lists:foreach(Print, Versions), + halt(0) + end. + + +-spec list_pending(package()) -> no_return(). + +list_pending(Package = {Realm, Name}) -> + ok = valid_package(Package), + Socket = connect_user(Realm), + ok = send(Socket, {pending, Package}), + case recv_or_die(Socket) of + {ok, []} -> + Message = "Package ~ts-~ts has no versions pending.", + ok = log(info, Message, [Realm, Name]), + halt(0); + {ok, Versions} -> + Print = + fun(Version) -> + PackageString = package_string({Realm, Name, Version}), + io:format("~ts~n", [PackageString]) + end, + ok = lists:foreach(Print, Versions), + halt(0) + end. + + +-spec valid_package(package()) -> ok | no_return(). + +valid_package({Realm, Name}) -> + case {valid_lower0_9(Realm), valid_lower0_9(Name)} of + {true, true} -> + ok; + {false, true} -> + ok = log(error, "Invalid realm name: ~tp", [Realm]), + halt(1); + {true, false} -> + ok = log(error, "Invalid package name: ~tp", [Name]), + halt(1); + {false, false} -> + ok = log(error, "Invalid realm ~tp and package ~tp", [Realm, Name]), + halt(1) + end. + + + %%% Add realm -spec add_realm(Path) -> no_return() @@ -621,35 +769,95 @@ add_package(PackageName) -> %% This sysop-only command can add a package to a realm operated by the caller. add_package(Realm, Name) -> - Socket = - case connect_auth(Realm) of - {ok, S} -> - S; - Error -> - M1 = "Connection failed to realm prime with ~160tp.", - ok = log(warning, M1, [Error]), - halt(1) - end, + Socket = connect_auth_or_die(Realm), ok = send(Socket, {add_package, {Realm, Name}}), - receive - {tcp, Socket, Bin} -> - case binary_to_term(Bin, [safe]) of - ok -> - ok = log(info, "\"~ts-~ts\" added successfully.", [Realm, Name]), - halt(0); - {error, Reason} -> - M2 = "Operation failed. Server sends reason: ~160tp", - ok = log(error, M2, [Reason]), - halt(1) - end; - {tcp_closed, Socket} -> - halt_on_unexpected_close() - after 5000 -> - ok = log(warning, "Operation timed After submission to server."), - halt(1) + ok = recv_or_die(Socket), + ok = log(info, "\"~ts-~ts\" added successfully.", [Realm, Name]), + halt(0). + + +list_resigns(Realm) -> + Socket = connect_auth_or_die(Realm), + ok = send(Socket, {list_resigns, Realm}), + case recv_or_die(Socket) of + {ok, []} -> + Message = "No packages pending signature in ~tp.", + ok = log(info, Message, [Realm]), + halt(0); + {ok, PackageIDs} -> + Print = + fun(PackageID) -> + PackageString = package_string(PackageID), + io:format("~ts~n", [PackageString]) + end, + ok = lists:foreach(Print, PackageIDs), + halt(0) end. +add_packager(Package, UserName) -> + ok = log(info, "Would add ~ts to packagers for ~160tp now.", [UserName, Package]), + halt(0). + + +add_maintainer(Package, UserName) -> + ok = log(info, "Would add ~ts to maintainer for ~160tp now.", [UserName, Package]), + halt(0). + + +review(PackageID) -> + PackageString = package_string(PackageID), + ZrpPath = PackageString ++ ".zrp", + ok = log(info, "Saving to ~ts, unpacking to ./~ts/", [ZrpPath, PackageString]), + ok = + case {filelib:is_file(ZrpPath), filelib:is_file(PackageString)} of + {false, false} -> + ok; + {true, false} -> + ok = log(error, "~ts already exists. Aborting.", [ZrpPath]), + halt(1); + {false, true} -> + ok = log(error, "~ts already exists. Aborting.", [PackageString]), + halt(1); + {true true} -> + Message = "~ts and ~ts already exist. Aborting.", + ok = log(error, Message, [ZrpPath, PackageString]), + halt(1); + end, + Socket = connect_auth_or_die(), + ok = send(Socket, {review, PackageID}), + ok = receive_or_die(Socket), + {ok, ZrpBin} = recieve_or_die(Socket), + ok = file:write_file(ZrpPath, ZrpBin), + ok = disconnect(Socket), + {"zomp.meta", MetaBin} = erl_tar:extract(ZrpBin, [memory, {files, "zomp.meta"}]), + Meta = binary_to_term(MetaBin, [safe]), + + + +approve(PackageID = {Realm, _, _}) -> + Socket = connect_auth_or_die(Realm), + ok = send(Socket, {approve, PackageID}), + ok = recv_or_die(Socket), + ok = log(info, "ok"), + halt(0). + + +reject(PackageID = {Realm, _, _}) -> + Socket = connect_auth_or_die(Realm), + ok = send(Socket, {reject, PackageID}), + ok = recv_or_die(Socket), + ok = log(info, "ok"), + halt(0). + + +resign(PackageID) -> + M = "Pull the indicated package", + ok = log(info, M, [PackageID]), + halt(0). + + + %%% Drop dependency -spec drop_dep(package_id()) -> no_return(). @@ -1057,6 +1265,43 @@ send(Socket, Message) -> gen_tcp:send(Socket, Bin). +-spec recv_or_die(Socket) -> Result | no_return() + when Socket :: gen_tcp:socket(), + Result :: ok | {ok, term()}. + +recv_or_die(Socket) -> + receive + {tcp, Socket, Bin} -> + case binary_to_term(Bin, [safe]) of + ok -> + ok; + {ok, Response} -> + {ok, Response}; + {error, bad_realm} -> + ok = log(warning, "No such realm at the connected node."). + halt(1); + {error, bad_package} -> + ok = log(warning, "No such package."). + halt(1) + {error, bad_version} -> + ok = log(warning, "No such version."), + halt(1); + {error, not_in_queue} -> + ok = log(warning, "Version is not queued."), + halt(1); + {error, bad_message} -> + ok = log(error, "Oh noes! zx sent an illegal message!"), + halt(1) + end; + {tcp_closed, Socket} -> + ok = log(warning, "Lost connection to node unexpectedly."), + halt(1) + after 5000 -> + ok = log(warning, "Node timed out."), + halt(1) + end. + + -spec halt_on_unexpected_close() -> no_return(). halt_on_unexpected_close() -> @@ -1169,7 +1414,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, {Realm, Serials}), + NewSerials = lists:keystore(Realm, 1, Serials, {Realm, Current}), {ok, Host} = inet:peername(Socket), ok = write_terms(hosts_cache_file(Realm), [Host | Hosts]), ok = write_terms(SerialFile, NewSerials), @@ -1193,6 +1438,19 @@ confirm_serial(Realm, Socket, Hosts) -> end. +-spec connect_auth_or_die(realm()) -> gen_tcp:socket() | no_return(). + +connect_auth_or_die(Realm) -> + case connect_auth(Realm) of + {ok, Socket} -> + Socket; + Error -> + M1 = "Connection failed to realm prime with ~160tp.", + ok = log(warning, M1, [Error]), + halt(1) + end. + + -spec connect_auth(Realm) -> Result when Realm :: realm(), Result :: {ok, gen_tcp:socket()}