/src/distfs_iclient.erl
Erlang | 278 lines | 219 code | 30 blank | 29 comment | 6 complexity | 7c9452493c07003e22d91dfd0f157b45 MD5 | raw file
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%% Interactive client for the distributed file system. %%%
- %%% %%%
- %%% This module is intended to be used by end users. %%%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%% 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_iclient).
- -export([shell/0]).
- -export([exit_/2, status_/2, connect_/2, disconnect_/2, list_/2, create_/2,
- delete_/2]).
- -define(PROMPT, "\033[1mDistFS> \033[0m").
- -record(state, {commands, socket}).
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% API function %%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- shell() ->
- error_logger:tty(false), % Disable server output.
- Commands = orddict:from_list([{"help", fun help_/2},
- {"exit", fun exit_/2},
- {"status", fun status_/2},
- {"connect", fun connect_/2},
- {"disconnect", fun disconnect_/2},
- {"list", fun list_/2},
- {"create", fun create_/2},
- {"delete", fun delete_/2},
- {"open", fun open_/2},
- {"close", fun close_/2},
- {"read", fun read_/2},
- {"write", fun write_/2}]),
- try shell(#state{commands = Commands})
- catch
- throw:halt -> halt()
- end,
- halt().
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Private functions %%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- shell(State) ->
- Line = get_line_or_halt(),
- Parts = string:tokens(Line, " \t\n"),
- case Parts of
- [] ->
- shell(State);
- [RawCommand | Args] ->
- Command = string:to_lower(RawCommand),
- case orddict:find(Command, State#state.commands) of
- {ok, CommandFun} ->
- try CommandFun(Args, State) of
- NewSocket -> shell(State#state{socket = NewSocket})
- catch
- throw:exit ->
- ok;
- throw:continue ->
- shell(State);
- error:function_clause ->
- print_error("invalid number of arguments "
- "in '~s'",
- [string:strip(Line, right, $\n)]),
- shell(State)
- end;
- error ->
- print_error("invalid command '~s'", [Command]),
- shell(State)
- end
- end.
- get_line_or_halt() ->
- case io:get_line(?PROMPT) of
- eof ->
- io:format("~n", []),
- throw(halt);
- X ->
- X
- end.
- print_error(String) ->
- print_error(String, []).
- print_error(Format, Args) ->
- io:format("ERROR: " ++ Format ++ ".\n", Args).
- print_protocol_error(Code) ->
- String = distfs_parser:errorcode_to_string(Code),
- print_error("~p (~s)", [Code, String]).
- check_connection(undefined) ->
- print_error("no connection established"),
- throw(continue);
- check_connection(_Socket) ->
- ok.
- my_list_to_fileid(FileIdString) ->
- try list_to_integer(FileIdString)
- catch
- error:badarg ->
- print_error("'~s' is not a file identifier", [FileIdString]),
- throw(continue)
- end.
- my_list_to_natural(NaturalString) ->
- try list_to_integer(NaturalString)
- catch
- error:badarg ->
- print_error("'~s' is not a natural number", [NaturalString]),
- throw(continue)
- end.
- %% Functions for shell commands.
- help_([], #state{socket = Socket, commands = Commands}) ->
- io:format("Available commands: '~s'.\n",
- [string:join(orddict:fetch_keys(Commands), "', '")]),
- Socket.
- exit_([], _State) ->
- throw(exit).
- connect_([], State) ->
- connect_(["localhost"], State);
- connect_([Location], #state{socket = Socket}) ->
- if Socket == undefined -> ok;
- true -> distfs_client:disconnect(Socket)
- end,
- case distfs_client:connect(Location) of
- {ok, NewSocket} ->
- NewSocket;
- {error, Code} ->
- print_protocol_error(Code),
- undefined
- end;
- connect_([Location, PortString], #state{socket = Socket}) ->
- Port = my_list_to_natural(PortString),
- if Socket == undefined -> ok;
- true -> distfs_client:disconnect(Socket)
- end,
- case distfs_client:connect(Location, Port) of
- {ok, NewSocket} ->
- NewSocket;
- {error, Code} ->
- print_protocol_error(Code),
- undefined
- end.
- disconnect_([], #state{socket = Socket}) ->
- check_connection(Socket),
- distfs_client:disconnect(Socket),
- undefined.
- status_([], #state{socket = Socket}) ->
- if Socket == undefined ->
- io:format("Status: not connected.\n", []);
- true ->
- io:format("Status: connected to ~s.\n",
- [distfs_utils:get_peer_string(Socket)])
- end,
- Socket.
- list_([], #state{socket = Socket}) ->
- check_connection(Socket),
- case distfs_client:list(Socket) of
- {ok, []} ->
- ok;
- {ok, Files} ->
- io:format("~s~n", [string:join(Files, " ")]);
- {error, Code} ->
- print_protocol_error(Code)
- end,
- Socket.
- create_(Files, #state{socket = Socket}) ->
- check_connection(Socket),
- lists:foreach(fun (File) ->
- case distfs_client:create(Socket, File) of
- ok ->
- ok;
- {error, Reason} ->
- print_error("'~s' could not be created "
- "because of '~p'",
- [File, Reason])
- end
- end, Files),
- Socket.
- delete_(Files, #state{socket = Socket}) ->
- check_connection(Socket),
- lists:foreach(fun (File) ->
- case distfs_client:delete(Socket, File) of
- ok ->
- ok;
- {error, Reason} ->
- print_error("'~s' could not be deleted "
- "because of '~p'",
- [File, Reason])
- end
- end, Files),
- Socket.
- open_(Files, #state{socket = Socket}) ->
- check_connection(Socket),
- lists:foreach(fun (File) ->
- case distfs_client:open(Socket, File) of
- {ok, FileId} ->
- io:format("~p\n", [FileId]);
- {error, Reason} ->
- print_error("'~s' could not be opened "
- "because of '~p'",
- [File, Reason])
- end
- end, Files),
- Socket.
- close_(FileIdStrings, #state{socket = Socket}) ->
- check_connection(Socket),
- lists:foreach(fun (FileIdString) ->
- FileId = my_list_to_fileid(FileIdString),
- case distfs_client:close(Socket, FileId) of
- ok ->
- ok;
- {error, Reason} ->
- print_error("'~s' could not be closed "
- "because of '~p'",
- [FileIdString, Reason])
- end
- end, FileIdStrings),
- Socket.
- read_([FileIdString, SizeString], #state{socket = Socket}) ->
- check_connection(Socket),
- FileId = my_list_to_fileid(FileIdString),
- Size = my_list_to_natural(SizeString),
- case distfs_client:read(Socket, FileId, Size) of
- eof ->
- print_error("end of file");
- {ok, Data} ->
- io:format("~p bytes read.\n~s\n", [length(Data), Data]);
- {error, Reason} ->
- print_error("'~p'", [Reason])
- end,
- Socket.
- write_([FileIdString | DataParts], #state{socket = Socket}) ->
- check_connection(Socket),
- FileId = my_list_to_fileid(FileIdString),
- Data = string:join(DataParts, " "),
- case distfs_client:write(Socket, FileId, Data) of
- {ok, Size} ->
- io:format("~p bytes written.\n", [Size]);
- {error, Reason} ->
- print_error("'~s' could not be written into '~s' because of "
- "'~p'", [Data, FileIdString, Reason])
- end,
- Socket.