/src/distfs_client.erl
Erlang | 217 lines | 105 code | 26 blank | 86 comment | 0 complexity | 117a3331adc2dc8a266d6fcbcfef08b2 MD5 | raw file
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%% Client for the distributed file system. %%%
- %%% %%%
- %%% This module is intended to be used by programmers, as a client %%%
- %%% library. %%%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%% Copyright © 2014, 2015 Mariano Street. %%%
- %%% %%%
- %%% This file is part of DistFS. %%%
- %%% %%%
- %%% DistFS is free software: you can redistribute it and/or modify %%%
- %%% it under the terms of the GNU General Public License as published by %%%
- %%% the Free Software Foundation, either version 3 of the License, or %%%
- %%% (at your option) any later version. %%%
- %%% %%%
- %%% DistFS is distributed in the hope that it will be useful, %%%
- %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%%
- %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%%
- %%% GNU General Public License for more details. %%%
- %%% %%%
- %%% You should have received a copy of the GNU General Public License %%%
- %%% along with DistFS. If not, see <http://www.gnu.org/licenses/>. %%%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- -module(distfs_client).
- -export([connect/1, connect/2, disconnect/1, list/1, create/2, delete/2,
- open/2, close/2, read/3, write/3]).
- -define(DEFAULT_PORT, 8000).
- -define(TIMEOUT, 15000).
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Types %%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Private.
- -type location() :: inet:ip_address() | inet:hostname().
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% API functions %%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Connect to a remote file system server on the default port.
- %%
- -spec connect(location()) -> {ok, port()} | {error, _}.
- connect(Location) ->
- connect(Location, ?DEFAULT_PORT).
- %% Connect to a remote file system server.
- %%
- -spec connect(location(), inet:port_number()) -> {ok, port()} | {error, _}.
- connect(Location, Port) ->
- case gen_tcp:connect(Location, Port, [{active, false}]) of
- {ok, Socket} ->
- send_line(Socket, ["CON"]),
- case receive_void_answer(Socket) of
- ok -> {ok, Socket};
- Error -> Error
- end;
- X ->
- X
- end.
- -spec disconnect(port()) -> _.
- disconnect(Socket) ->
- gen_tcp:close(Socket).
- %% List files from the remote file system. This function is synchronous.
- %%
- -spec list(port()) -> {ok, [distfs_parser:filename()]} | {error, _}.
- list(Socket) ->
- send_line(Socket, ["LSD"]),
- receive_list_answer(Socket).
- %% Create a file in the remote file system. This function is synchronous.
- %%
- -spec create(port(), distfs_parser:filename()) -> ok | {error, _}.
- create(Socket, FileName) ->
- send_line(Socket, ["CRE", FileName]),
- receive_void_answer(Socket).
- %% Delete a file in the remote file system. This function is synchronous.
- %%
- -spec delete(port(), distfs_parser:filename()) -> ok | {error, _}.
- delete(Socket, FileName) ->
- send_line(Socket, ["DEL", FileName]),
- receive_void_answer(Socket).
- %% Open a file in the remote file system. This function is synchronous.
- %%
- -spec open(port(), distfs_parser:filename()) -> {ok, _} | {error, _}.
- open(Socket, FileName) ->
- send_line(Socket, ["OPN", FileName]),
- receive_integer_answer(Socket).
- %% Close a file in the remote file system. This function is synchronous.
- %%
- -spec close(port(), distfs_worker:fileid()) -> ok | {error, _}.
- close(Socket, FileId) ->
- FileIdString = integer_to_list(FileId),
- send_line(Socket, ["CLO", FileIdString]),
- receive_void_answer(Socket).
- %% Read data from an open file in the remote file system. This function is
- %% synchronous.
- %%
- -spec read(port(), distfs_worker:fileid(), non_neg_integer()) ->
- {ok, string()} | eof | {error, _}.
- read(Socket, FileId, Size) ->
- FileIdString = integer_to_list(FileId),
- SizeString = integer_to_list(Size),
- send_line(Socket, ["REA", FileIdString, SizeString]),
- case receive_answer(Socket) of
- {ok, "E\n"} ->
- eof;
- {ok, "0\n"} ->
- {ok, ""};
- {ok, X} ->
- Data = string:substr(X, string:str(X, " ") + 1),
- {ok, strip_last_newline(Data)};
- R ->
- R
- end.
- %% Write data into an open file in the remote file system. This function is
- %% synchronous.
- %%
- -spec write(port(), distfs_worker:fileid(), string()) -> ok | {error, _}.
- write(Socket, FileId, Data) ->
- FileIdString = integer_to_list(FileId),
- send_line(Socket, ["WRT", FileIdString,
- integer_to_list(length(Data))]),
- gen_tcp:send(Socket, Data),
- receive_integer_answer(Socket).
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Private functions %%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Send a command line to the server asynchorously, i.e. returning
- %% immediately, without waiting for an answer.
- %%
- %% Parameters:
- %%
- %% * `Socket`: a socket for communicating with the server; a connection need
- %% already be established.
- %% * `Parts`: a list of strings that correspond to the parts of the command
- %% line to be sent, beginning with the command name and continuing
- %% with optional command arguments.
- %%
- -spec send_line(port(), [string(), ...]) -> _.
- send_line(Socket, Parts) ->
- % Build the command line.
- Line = string:join(Parts, " ") ++ "\n",
- % Send it to the server.
- gen_tcp:send(Socket, Line).
- %% Wait for an answer from the server to an already sent command line.
- %%
- %% Parameters:
- %%
- %% * `Socket`: a socket for communicating with the server; a connection need
- %% already be established.
- %%
- %% Return value: a pair representing the server's answer; the first element
- %% is either the atom `ok` or `error`, and the second one is a
- %% string that represents data sent by the server as answer.
- %%
- -spec receive_answer(port()) -> {ok, _} | {error, _}.
- receive_answer(Socket) ->
- case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
- {ok, "OK " ++ Data} -> {ok, Data};
- {ok, "OK\n"} -> {ok, "\n"};
- {ok, "ERR " ++ Error} -> {error, error_code(Error)};
- Error -> Error
- end.
- -spec receive_void_answer(port()) -> ok | {error, _}.
- receive_void_answer(Socket) ->
- case receive_answer(Socket) of
- {ok, "\n"} -> ok;
- Error -> Error
- end.
- -spec receive_list_answer(port()) -> {ok, [string()]} | {error, _}.
- receive_list_answer(Socket) ->
- case receive_answer(Socket) of
- {ok, Data} -> {ok, string:tokens(Data, " \n")};
- Error -> Error
- end.
- -spec receive_integer_answer(port()) -> {ok, integer()} | {error, _}.
- receive_integer_answer(Socket) ->
- case receive_answer(Socket) of
- {ok, Data} -> {ok, list_to_integer(string:strip(Data, right, $\n))};
- Error -> Error
- end.
- %% Get the error code number that corresponds to an error code string.
- %%
- %% Parameters:
- %%
- %% * `ErrorString`: error string as sent by the server without the "ERR "
- %% prefix.
- %%
- %% Return value: an integer that represents the error code number.
- %%
- -spec error_code(nonempty_string()) -> distfs_parser:error_code().
- error_code(ErrorString) ->
- list_to_integer(string:strip(ErrorString, right, $\n)).
- strip_last_newline([]) -> [];
- strip_last_newline([$\n]) -> [];
- strip_last_newline([H | D]) -> [H | strip_last_newline(D)].