PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/distfs_client.erl

https://gitlab.com/mstreet_fceia/distfs
Erlang | 217 lines | 105 code | 26 blank | 86 comment | 0 complexity | 117a3331adc2dc8a266d6fcbcfef08b2 MD5 | raw file
  1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  2. %%% Client for the distributed file system. %%%
  3. %%% %%%
  4. %%% This module is intended to be used by programmers, as a client %%%
  5. %%% library. %%%
  6. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  7. %%% Copyright © 2014, 2015 Mariano Street. %%%
  8. %%% %%%
  9. %%% This file is part of DistFS. %%%
  10. %%% %%%
  11. %%% DistFS is free software: you can redistribute it and/or modify %%%
  12. %%% it under the terms of the GNU General Public License as published by %%%
  13. %%% the Free Software Foundation, either version 3 of the License, or %%%
  14. %%% (at your option) any later version. %%%
  15. %%% %%%
  16. %%% DistFS is distributed in the hope that it will be useful, %%%
  17. %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%%
  18. %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%%
  19. %%% GNU General Public License for more details. %%%
  20. %%% %%%
  21. %%% You should have received a copy of the GNU General Public License %%%
  22. %%% along with DistFS. If not, see <http://www.gnu.org/licenses/>. %%%
  23. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  24. -module(distfs_client).
  25. -export([connect/1, connect/2, disconnect/1, list/1, create/2, delete/2,
  26. open/2, close/2, read/3, write/3]).
  27. -define(DEFAULT_PORT, 8000).
  28. -define(TIMEOUT, 15000).
  29. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  30. %% Types %%
  31. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  32. %% Private.
  33. -type location() :: inet:ip_address() | inet:hostname().
  34. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  35. %% API functions %%
  36. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  37. %% Connect to a remote file system server on the default port.
  38. %%
  39. -spec connect(location()) -> {ok, port()} | {error, _}.
  40. connect(Location) ->
  41. connect(Location, ?DEFAULT_PORT).
  42. %% Connect to a remote file system server.
  43. %%
  44. -spec connect(location(), inet:port_number()) -> {ok, port()} | {error, _}.
  45. connect(Location, Port) ->
  46. case gen_tcp:connect(Location, Port, [{active, false}]) of
  47. {ok, Socket} ->
  48. send_line(Socket, ["CON"]),
  49. case receive_void_answer(Socket) of
  50. ok -> {ok, Socket};
  51. Error -> Error
  52. end;
  53. X ->
  54. X
  55. end.
  56. -spec disconnect(port()) -> _.
  57. disconnect(Socket) ->
  58. gen_tcp:close(Socket).
  59. %% List files from the remote file system. This function is synchronous.
  60. %%
  61. -spec list(port()) -> {ok, [distfs_parser:filename()]} | {error, _}.
  62. list(Socket) ->
  63. send_line(Socket, ["LSD"]),
  64. receive_list_answer(Socket).
  65. %% Create a file in the remote file system. This function is synchronous.
  66. %%
  67. -spec create(port(), distfs_parser:filename()) -> ok | {error, _}.
  68. create(Socket, FileName) ->
  69. send_line(Socket, ["CRE", FileName]),
  70. receive_void_answer(Socket).
  71. %% Delete a file in the remote file system. This function is synchronous.
  72. %%
  73. -spec delete(port(), distfs_parser:filename()) -> ok | {error, _}.
  74. delete(Socket, FileName) ->
  75. send_line(Socket, ["DEL", FileName]),
  76. receive_void_answer(Socket).
  77. %% Open a file in the remote file system. This function is synchronous.
  78. %%
  79. -spec open(port(), distfs_parser:filename()) -> {ok, _} | {error, _}.
  80. open(Socket, FileName) ->
  81. send_line(Socket, ["OPN", FileName]),
  82. receive_integer_answer(Socket).
  83. %% Close a file in the remote file system. This function is synchronous.
  84. %%
  85. -spec close(port(), distfs_worker:fileid()) -> ok | {error, _}.
  86. close(Socket, FileId) ->
  87. FileIdString = integer_to_list(FileId),
  88. send_line(Socket, ["CLO", FileIdString]),
  89. receive_void_answer(Socket).
  90. %% Read data from an open file in the remote file system. This function is
  91. %% synchronous.
  92. %%
  93. -spec read(port(), distfs_worker:fileid(), non_neg_integer()) ->
  94. {ok, string()} | eof | {error, _}.
  95. read(Socket, FileId, Size) ->
  96. FileIdString = integer_to_list(FileId),
  97. SizeString = integer_to_list(Size),
  98. send_line(Socket, ["REA", FileIdString, SizeString]),
  99. case receive_answer(Socket) of
  100. {ok, "E\n"} ->
  101. eof;
  102. {ok, "0\n"} ->
  103. {ok, ""};
  104. {ok, X} ->
  105. Data = string:substr(X, string:str(X, " ") + 1),
  106. {ok, strip_last_newline(Data)};
  107. R ->
  108. R
  109. end.
  110. %% Write data into an open file in the remote file system. This function is
  111. %% synchronous.
  112. %%
  113. -spec write(port(), distfs_worker:fileid(), string()) -> ok | {error, _}.
  114. write(Socket, FileId, Data) ->
  115. FileIdString = integer_to_list(FileId),
  116. send_line(Socket, ["WRT", FileIdString,
  117. integer_to_list(length(Data))]),
  118. gen_tcp:send(Socket, Data),
  119. receive_integer_answer(Socket).
  120. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  121. %% Private functions %%
  122. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  123. %% Send a command line to the server asynchorously, i.e. returning
  124. %% immediately, without waiting for an answer.
  125. %%
  126. %% Parameters:
  127. %%
  128. %% * `Socket`: a socket for communicating with the server; a connection need
  129. %% already be established.
  130. %% * `Parts`: a list of strings that correspond to the parts of the command
  131. %% line to be sent, beginning with the command name and continuing
  132. %% with optional command arguments.
  133. %%
  134. -spec send_line(port(), [string(), ...]) -> _.
  135. send_line(Socket, Parts) ->
  136. % Build the command line.
  137. Line = string:join(Parts, " ") ++ "\n",
  138. % Send it to the server.
  139. gen_tcp:send(Socket, Line).
  140. %% Wait for an answer from the server to an already sent command line.
  141. %%
  142. %% Parameters:
  143. %%
  144. %% * `Socket`: a socket for communicating with the server; a connection need
  145. %% already be established.
  146. %%
  147. %% Return value: a pair representing the server's answer; the first element
  148. %% is either the atom `ok` or `error`, and the second one is a
  149. %% string that represents data sent by the server as answer.
  150. %%
  151. -spec receive_answer(port()) -> {ok, _} | {error, _}.
  152. receive_answer(Socket) ->
  153. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  154. {ok, "OK " ++ Data} -> {ok, Data};
  155. {ok, "OK\n"} -> {ok, "\n"};
  156. {ok, "ERR " ++ Error} -> {error, error_code(Error)};
  157. Error -> Error
  158. end.
  159. -spec receive_void_answer(port()) -> ok | {error, _}.
  160. receive_void_answer(Socket) ->
  161. case receive_answer(Socket) of
  162. {ok, "\n"} -> ok;
  163. Error -> Error
  164. end.
  165. -spec receive_list_answer(port()) -> {ok, [string()]} | {error, _}.
  166. receive_list_answer(Socket) ->
  167. case receive_answer(Socket) of
  168. {ok, Data} -> {ok, string:tokens(Data, " \n")};
  169. Error -> Error
  170. end.
  171. -spec receive_integer_answer(port()) -> {ok, integer()} | {error, _}.
  172. receive_integer_answer(Socket) ->
  173. case receive_answer(Socket) of
  174. {ok, Data} -> {ok, list_to_integer(string:strip(Data, right, $\n))};
  175. Error -> Error
  176. end.
  177. %% Get the error code number that corresponds to an error code string.
  178. %%
  179. %% Parameters:
  180. %%
  181. %% * `ErrorString`: error string as sent by the server without the "ERR "
  182. %% prefix.
  183. %%
  184. %% Return value: an integer that represents the error code number.
  185. %%
  186. -spec error_code(nonempty_string()) -> distfs_parser:error_code().
  187. error_code(ErrorString) ->
  188. list_to_integer(string:strip(ErrorString, right, $\n)).
  189. strip_last_newline([]) -> [];
  190. strip_last_newline([$\n]) -> [];
  191. strip_last_newline([H | D]) -> [H | strip_last_newline(D)].