PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/distfs_iclient.erl

https://gitlab.com/mstreet_fceia/distfs
Erlang | 278 lines | 219 code | 30 blank | 29 comment | 6 complexity | 7c9452493c07003e22d91dfd0f157b45 MD5 | raw file
  1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  2. %%% Interactive client for the distributed file system. %%%
  3. %%% %%%
  4. %%% This module is intended to be used by end users. %%%
  5. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  6. %%% Copyright © 2014, 2015 Mariano Street. %%%
  7. %%% %%%
  8. %%% This file is part of DistFS. %%%
  9. %%% %%%
  10. %%% DistFS is free software: you can redistribute it and/or modify %%%
  11. %%% it under the terms of the GNU General Public License as published by %%%
  12. %%% the Free Software Foundation, either version 3 of the License, or %%%
  13. %%% (at your option) any later version. %%%
  14. %%% %%%
  15. %%% DistFS is distributed in the hope that it will be useful, %%%
  16. %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%%
  17. %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%%
  18. %%% GNU General Public License for more details. %%%
  19. %%% %%%
  20. %%% You should have received a copy of the GNU General Public License %%%
  21. %%% along with DistFS. If not, see <http://www.gnu.org/licenses/>. %%%
  22. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  23. -module(distfs_iclient).
  24. -export([shell/0]).
  25. -export([exit_/2, status_/2, connect_/2, disconnect_/2, list_/2, create_/2,
  26. delete_/2]).
  27. -define(PROMPT, "\033[1mDistFS> \033[0m").
  28. -record(state, {commands, socket}).
  29. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  30. %% API function %%
  31. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  32. shell() ->
  33. error_logger:tty(false), % Disable server output.
  34. Commands = orddict:from_list([{"help", fun help_/2},
  35. {"exit", fun exit_/2},
  36. {"status", fun status_/2},
  37. {"connect", fun connect_/2},
  38. {"disconnect", fun disconnect_/2},
  39. {"list", fun list_/2},
  40. {"create", fun create_/2},
  41. {"delete", fun delete_/2},
  42. {"open", fun open_/2},
  43. {"close", fun close_/2},
  44. {"read", fun read_/2},
  45. {"write", fun write_/2}]),
  46. try shell(#state{commands = Commands})
  47. catch
  48. throw:halt -> halt()
  49. end,
  50. halt().
  51. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  52. %% Private functions %%
  53. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  54. shell(State) ->
  55. Line = get_line_or_halt(),
  56. Parts = string:tokens(Line, " \t\n"),
  57. case Parts of
  58. [] ->
  59. shell(State);
  60. [RawCommand | Args] ->
  61. Command = string:to_lower(RawCommand),
  62. case orddict:find(Command, State#state.commands) of
  63. {ok, CommandFun} ->
  64. try CommandFun(Args, State) of
  65. NewSocket -> shell(State#state{socket = NewSocket})
  66. catch
  67. throw:exit ->
  68. ok;
  69. throw:continue ->
  70. shell(State);
  71. error:function_clause ->
  72. print_error("invalid number of arguments "
  73. "in '~s'",
  74. [string:strip(Line, right, $\n)]),
  75. shell(State)
  76. end;
  77. error ->
  78. print_error("invalid command '~s'", [Command]),
  79. shell(State)
  80. end
  81. end.
  82. get_line_or_halt() ->
  83. case io:get_line(?PROMPT) of
  84. eof ->
  85. io:format("~n", []),
  86. throw(halt);
  87. X ->
  88. X
  89. end.
  90. print_error(String) ->
  91. print_error(String, []).
  92. print_error(Format, Args) ->
  93. io:format("ERROR: " ++ Format ++ ".\n", Args).
  94. print_protocol_error(Code) ->
  95. String = distfs_parser:errorcode_to_string(Code),
  96. print_error("~p (~s)", [Code, String]).
  97. check_connection(undefined) ->
  98. print_error("no connection established"),
  99. throw(continue);
  100. check_connection(_Socket) ->
  101. ok.
  102. my_list_to_fileid(FileIdString) ->
  103. try list_to_integer(FileIdString)
  104. catch
  105. error:badarg ->
  106. print_error("'~s' is not a file identifier", [FileIdString]),
  107. throw(continue)
  108. end.
  109. my_list_to_natural(NaturalString) ->
  110. try list_to_integer(NaturalString)
  111. catch
  112. error:badarg ->
  113. print_error("'~s' is not a natural number", [NaturalString]),
  114. throw(continue)
  115. end.
  116. %% Functions for shell commands.
  117. help_([], #state{socket = Socket, commands = Commands}) ->
  118. io:format("Available commands: '~s'.\n",
  119. [string:join(orddict:fetch_keys(Commands), "', '")]),
  120. Socket.
  121. exit_([], _State) ->
  122. throw(exit).
  123. connect_([], State) ->
  124. connect_(["localhost"], State);
  125. connect_([Location], #state{socket = Socket}) ->
  126. if Socket == undefined -> ok;
  127. true -> distfs_client:disconnect(Socket)
  128. end,
  129. case distfs_client:connect(Location) of
  130. {ok, NewSocket} ->
  131. NewSocket;
  132. {error, Code} ->
  133. print_protocol_error(Code),
  134. undefined
  135. end;
  136. connect_([Location, PortString], #state{socket = Socket}) ->
  137. Port = my_list_to_natural(PortString),
  138. if Socket == undefined -> ok;
  139. true -> distfs_client:disconnect(Socket)
  140. end,
  141. case distfs_client:connect(Location, Port) of
  142. {ok, NewSocket} ->
  143. NewSocket;
  144. {error, Code} ->
  145. print_protocol_error(Code),
  146. undefined
  147. end.
  148. disconnect_([], #state{socket = Socket}) ->
  149. check_connection(Socket),
  150. distfs_client:disconnect(Socket),
  151. undefined.
  152. status_([], #state{socket = Socket}) ->
  153. if Socket == undefined ->
  154. io:format("Status: not connected.\n", []);
  155. true ->
  156. io:format("Status: connected to ~s.\n",
  157. [distfs_utils:get_peer_string(Socket)])
  158. end,
  159. Socket.
  160. list_([], #state{socket = Socket}) ->
  161. check_connection(Socket),
  162. case distfs_client:list(Socket) of
  163. {ok, []} ->
  164. ok;
  165. {ok, Files} ->
  166. io:format("~s~n", [string:join(Files, " ")]);
  167. {error, Code} ->
  168. print_protocol_error(Code)
  169. end,
  170. Socket.
  171. create_(Files, #state{socket = Socket}) ->
  172. check_connection(Socket),
  173. lists:foreach(fun (File) ->
  174. case distfs_client:create(Socket, File) of
  175. ok ->
  176. ok;
  177. {error, Reason} ->
  178. print_error("'~s' could not be created "
  179. "because of '~p'",
  180. [File, Reason])
  181. end
  182. end, Files),
  183. Socket.
  184. delete_(Files, #state{socket = Socket}) ->
  185. check_connection(Socket),
  186. lists:foreach(fun (File) ->
  187. case distfs_client:delete(Socket, File) of
  188. ok ->
  189. ok;
  190. {error, Reason} ->
  191. print_error("'~s' could not be deleted "
  192. "because of '~p'",
  193. [File, Reason])
  194. end
  195. end, Files),
  196. Socket.
  197. open_(Files, #state{socket = Socket}) ->
  198. check_connection(Socket),
  199. lists:foreach(fun (File) ->
  200. case distfs_client:open(Socket, File) of
  201. {ok, FileId} ->
  202. io:format("~p\n", [FileId]);
  203. {error, Reason} ->
  204. print_error("'~s' could not be opened "
  205. "because of '~p'",
  206. [File, Reason])
  207. end
  208. end, Files),
  209. Socket.
  210. close_(FileIdStrings, #state{socket = Socket}) ->
  211. check_connection(Socket),
  212. lists:foreach(fun (FileIdString) ->
  213. FileId = my_list_to_fileid(FileIdString),
  214. case distfs_client:close(Socket, FileId) of
  215. ok ->
  216. ok;
  217. {error, Reason} ->
  218. print_error("'~s' could not be closed "
  219. "because of '~p'",
  220. [FileIdString, Reason])
  221. end
  222. end, FileIdStrings),
  223. Socket.
  224. read_([FileIdString, SizeString], #state{socket = Socket}) ->
  225. check_connection(Socket),
  226. FileId = my_list_to_fileid(FileIdString),
  227. Size = my_list_to_natural(SizeString),
  228. case distfs_client:read(Socket, FileId, Size) of
  229. eof ->
  230. print_error("end of file");
  231. {ok, Data} ->
  232. io:format("~p bytes read.\n~s\n", [length(Data), Data]);
  233. {error, Reason} ->
  234. print_error("'~p'", [Reason])
  235. end,
  236. Socket.
  237. write_([FileIdString | DataParts], #state{socket = Socket}) ->
  238. check_connection(Socket),
  239. FileId = my_list_to_fileid(FileIdString),
  240. Data = string:join(DataParts, " "),
  241. case distfs_client:write(Socket, FileId, Data) of
  242. {ok, Size} ->
  243. io:format("~p bytes written.\n", [Size]);
  244. {error, Reason} ->
  245. print_error("'~s' could not be written into '~s' because of "
  246. "'~p'", [Data, FileIdString, Reason])
  247. end,
  248. Socket.