1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(ct_ftp).
22
23%% API
24-export([get/3,put/3, open/1,close/1, send/2,send/3,
25	 recv/2,recv/3, cd/2, ls/2, type/2, delete/2]).
26
27%% Callbacks
28-export([init/3,handle_msg/2,reconnect/2,terminate/2]).
29
30-include("ct_util.hrl").
31
32-record(state,{ftp_pid,target_name}).
33
34-define(DEFAULT_PORT,21).
35
36%%%=================================================================
37%%% API
38
39put(KeyOrName,LocalFile,RemoteFile) ->
40    Fun = fun(Ftp) -> send(Ftp,LocalFile,RemoteFile) end,
41    open_and_do(KeyOrName,Fun).
42
43get(KeyOrName,RemoteFile,LocalFile) ->
44    Fun = fun(Ftp) -> recv(Ftp,RemoteFile,LocalFile) end,
45    open_and_do(KeyOrName,Fun).
46
47open(KeyOrName) ->
48    case ct_util:get_key_from_name(KeyOrName) of
49	{ok,node} ->
50	    open(KeyOrName,"erlang","x");
51	_ ->
52	    case ct:get_config(KeyOrName) of
53		undefined ->
54		    log(heading(open,KeyOrName),"Failed: ~tp\n",
55			[{not_available,KeyOrName}]),
56		    {error,{not_available,KeyOrName}};
57		_ ->
58		    case ct:get_config({KeyOrName,username}) of
59			undefined ->
60			    log(heading(open,KeyOrName),"Failed: ~tp\n",
61				[{not_available,{KeyOrName,username}}]),
62			    {error,{not_available,{KeyOrName,username}}};
63			Username ->
64			    case ct:get_config({KeyOrName,password}) of
65				undefined ->
66				    log(heading(open,KeyOrName),"Failed: ~tp\n",
67					[{not_available,{KeyOrName,password}}]),
68				    {error,{not_available,{KeyOrName,password}}};
69				Password ->
70				    open(KeyOrName,Username,Password)
71			    end
72		    end
73	    end
74    end.
75
76open(KeyOrName,Username,Password) ->
77    log(heading(open,KeyOrName),"",[]),
78    case ct:get_config({KeyOrName,ftp}) of
79	undefined ->
80	    log(heading(open,KeyOrName),"Failed: ~tp\n",
81		[{not_available,{KeyOrName,ftp}}]),
82	    {error,{not_available,{KeyOrName,ftp}}};
83	Addr ->
84	    ct_gen_conn:start(KeyOrName,full_addr(Addr),{Username,Password},?MODULE)
85    end.
86
87send(Connection,LocalFile) ->
88    send(Connection,LocalFile,filename:basename(LocalFile)).
89
90send(Connection,LocalFile,RemoteFile) ->
91    case get_handle(Connection) of
92	{ok,Pid} ->
93	    call(Pid,{send,LocalFile,RemoteFile});
94	Error ->
95	    Error
96    end.
97
98recv(Connection,RemoteFile) ->
99    recv(Connection,RemoteFile,filename:basename(RemoteFile)).
100
101recv(Connection,RemoteFile,LocalFile) ->
102    case get_handle(Connection) of
103	{ok,Pid} ->
104	    call(Pid,{recv,RemoteFile,LocalFile});
105	Error ->
106	    Error
107    end.
108
109cd(Connection,Dir) ->
110    case get_handle(Connection) of
111	{ok,Pid} ->
112	    call(Pid,{cd,Dir});
113	Error ->
114	    Error
115    end.
116
117ls(Connection,Dir) ->
118    case get_handle(Connection) of
119	{ok,Pid} ->
120	    call(Pid,{ls,Dir});
121	Error ->
122	    Error
123    end.
124
125type(Connection,Type) ->
126    case get_handle(Connection) of
127	{ok,Pid} ->
128	    call(Pid,{type,Type});
129	Error ->
130	    Error
131    end.
132
133delete(Connection,File) ->
134    case get_handle(Connection) of
135	{ok,Pid} ->
136	    call(Pid,{delete,File});
137	Error ->
138	    Error
139    end.
140
141close(Connection) ->
142    case get_handle(Connection) of
143	{ok,Pid} ->
144	    ct_gen_conn:stop(Pid);
145	Error ->
146	    Error
147    end.
148
149
150%%%=================================================================
151%%% Callback functions
152
153init(KeyOrName,{IP,Port},{Username,Password}) ->
154    case ftp_connect(IP,Port,Username,Password) of
155	{ok,FtpPid} ->
156	    log(heading(init,KeyOrName),
157		"Opened ftp connection:\nIP: ~tp\nUsername: ~tp\nPassword: ~p\n",
158		[IP,Username,lists:duplicate(string:length(Password),$*)]),
159	    {ok,FtpPid,#state{ftp_pid=FtpPid,target_name=KeyOrName}};
160	Error ->
161	    Error
162    end.
163
164ftp_connect(IP,Port,Username,Password) ->
165    _ = ftp:start(),
166    case ftp:start_service([{host,IP},{port,Port}]) of
167	{ok,FtpPid} ->
168	    case ftp:user(FtpPid,Username,Password) of
169		ok ->
170		    {ok,FtpPid};
171		{error,Reason} ->
172		    {error,{user,Reason}}
173	    end;
174	{error,Reason} ->
175	    {error,{open,Reason}}
176    end.
177
178handle_msg({send,LocalFile,RemoteFile},State) ->
179    log(heading(send,State#state.target_name),
180	"LocalFile: ~tp\nRemoteFile: ~tp\n",[LocalFile,RemoteFile]),
181    Result = ftp:send(State#state.ftp_pid,LocalFile,RemoteFile),
182    {Result,State};
183handle_msg({recv,RemoteFile,LocalFile},State) ->
184    log(heading(recv,State#state.target_name),
185	"RemoteFile: ~tp\nLocalFile: ~tp\n",[RemoteFile,LocalFile]),
186    Result = ftp:recv(State#state.ftp_pid,RemoteFile,LocalFile),
187    {Result,State};
188handle_msg({cd,Dir},State) ->
189    log(heading(cd,State#state.target_name),"Dir: ~tp\n",[Dir]),
190    Result = ftp:cd(State#state.ftp_pid,Dir),
191    {Result,State};
192handle_msg({ls,Dir},State) ->
193    log(heading(ls,State#state.target_name),"Dir: ~tp\n",[Dir]),
194    Result = ftp:ls(State#state.ftp_pid,Dir),
195    {Result,State};
196handle_msg({type,Type},State) ->
197    log(heading(type,State#state.target_name),"Type: ~tp\n",[Type]),
198    Result = ftp:type(State#state.ftp_pid,Type),
199    {Result,State};
200handle_msg({delete,File},State) ->
201    log(heading(delete,State#state.target_name),"Delete file: ~tp\n",[File]),
202    Result = ftp:delete(State#state.ftp_pid,File),
203    {Result,State}.
204
205reconnect(_Addr,_State) ->
206    {error,no_reconnection_of_ftp}.
207
208terminate(FtpPid,State) ->
209    log(heading(terminate,State#state.target_name),
210	"Closing FTP connection.\nHandle: ~p\n",[FtpPid]),
211    ftp:stop_service(FtpPid).
212
213
214%%%=================================================================
215%%% Internal function
216get_handle(Pid) when is_pid(Pid) ->
217    {ok,Pid};
218get_handle(Name) ->
219    case ct_util:get_connection(Name,?MODULE) of
220	{ok,{Pid,_}} ->
221	    {ok,Pid};
222	{error,no_registered_connection} ->
223	    open(Name);
224	Error ->
225	    Error
226    end.
227
228full_addr({Ip,Port}) ->
229    {Ip,Port};
230full_addr(Ip) ->
231    {Ip,?DEFAULT_PORT}.
232
233call(Pid,Msg) ->
234    ct_gen_conn:call(Pid,Msg).
235
236
237heading(Function,Name) ->
238    io_lib:format("ct_ftp:~tw ~tp",[Function,Name]).
239
240log(Heading,Str,Args) ->
241    ct_gen_conn:log(Heading,Str,Args).
242
243
244open_and_do(Name,Fun) ->
245    case open(Name) of
246	{ok,Ftp} ->
247	    R = Fun(Ftp),
248	    close(Ftp),
249	    R;
250	Error ->
251	    Error
252    end.
253
254
255