%%----------------------------------------------------------------------
%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS" 
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%% 
%%----------------------------------------------------------------------
%% File        : CosFileTransfer_FileTransferSession_impl.erl
%% Description : 
%%
%% Created     : 12 Sept 2000
%%----------------------------------------------------------------------
-module('CosFileTransfer_FileTransferSession_impl').


%%----------------------------------------------------------------------
%% Include files
%%----------------------------------------------------------------------
-include_lib("orber/include/corba.hrl").
-include_lib("orber/src/orber_iiop.hrl").

-include("cosFileTransferApp.hrl").

%%----------------------------------------------------------------------
%% External exports
%%----------------------------------------------------------------------
-export([init/1,
	 terminate/2,
	 code_change/3,
	 handle_info/2]).

%% Interface functions
-export(['_get_protocols_supported'/2,
	 set_directory/3,
	 create_file/3,
	 create_directory/3,
	 get_file/3,
	 delete/3,
	 transfer/4,
	 append/4,
	 insert/5,
	 logout/2]).

%%----------------------------------------------------------------------
%% Internal exports
%%----------------------------------------------------------------------
-export([oe_orber_create_directory_current/2, oe_orber_get_content/4,
	 oe_orber_count_children/3]).
-export([invoke_call/3]).

%%----------------------------------------------------------------------
%% Records
%%----------------------------------------------------------------------
-record(state, {protocols, server, type, current, module, connection, mytype, 
		connection_timeout}).

%%----------------------------------------------------------------------
%% Macros
%%----------------------------------------------------------------------
-define(create_InitState(P, S, T, C, M, Co, Ty, CT),
	#state{protocols=P, server=S, type=T, current=C, module=M, connection=Co,
	       mytype=Ty, connection_timeout=CT}).

-define(get_Protocols(S),         S#state.protocols).
-define(get_Server(S),            S#state.server).
-define(get_CurrentDir(S),        S#state.current).
-define(get_Module(S),            S#state.module).
-define(get_Connection(S),        S#state.connection).
-define(get_MyType(S),            S#state.mytype).
-define(get_ConnectionTimeout(S), S#state.connection_timeout).
-define(set_CurrentDir(S, C),     S#state{current=C}).

-define(is_FTP(S),                S#state.type=='FTP').
-define(is_FTAM(S),               S#state.type=='FTAM').
-define(is_NATIVE(S),             S#state.type=='NATIVE').
-define(is_ORBER_NATIVE(S),       S#state.module==cosFileTransferNATIVE_file).


%%======================================================================
%% External functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function   : init/1
%% Returns    : {ok, State}          |
%%              {ok, State, Timeout} |
%%              ignore               |
%%              {stop, Reason}
%% Description: Initiates the server
%%----------------------------------------------------------------------
init(['FTP', Host, Port, User, Password, _Account, Protocol, Timeout]) ->
    {ok, Pid} = ftp:open(Host, Port, []),
    ok = ftp:user(Pid, User, Password),
    {ok, PWD} = ftp:pwd(Pid),
    {Connection, ProtocolSupport} = setup_local(Protocol),
    {ok, ?create_InitState(ProtocolSupport, Pid, 'FTP', 
			   PWD, ftp, Connection, Protocol, Timeout)};
init([{'NATIVE', Mod}, Host, Port, User, Password, _Account, Protocol, Timeout]) ->
    {ok, Pid} = Mod:open(Host, Port),
    ok = Mod:user(Pid, User, Password),
    {ok, PWD} = Mod:pwd(Pid),
    {Connection, ProtocolSupport} = setup_local(Protocol),
    {ok, ?create_InitState(ProtocolSupport, Pid, 'NATIVE', 
			   PWD, Mod, Connection, Protocol, Timeout)}.
    

%%----------------------------------------------------------------------
%% Function   : terminate/2
%% Returns    : any (ignored by gen_server)
%% Description: Shutdown the server
%%----------------------------------------------------------------------
terminate(_Reason, State) ->
    case ?get_MyType(State) of
	ssl ->
	    catch ssl:close(?get_Connection(State));
	_ ->
	    catch gen_tcp:close(?get_Connection(State))
    end,
    ok.

%%----------------------------------------------------------------------
%% Function   : code_change/3
%% Returns    : {ok, NewState}
%% Description: Convert process state when code is changed
%%----------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%---------------------------------------------------------------------%
%% function : handle_info/2
%% Arguments: 
%% Returns  : 
%% Effect   : 
%%----------------------------------------------------------------------
handle_info(Info, State) ->
    case Info of
        {'EXIT', _Pid, Reason} ->
            {stop, Reason, State};
        _Other ->
            {noreply, State}
    end.
 
%%======================================================================
%% CosFileTransfer::FileTransferSession
%%======================================================================
%%---------------------------------------------------------------------%
%% Function   : _get_protocols_supported
%% Arguments  : 
%% Returns    : A list of CosFileTransfer::ProtocolSupport, i.e.,
%%              struct ProtocolSupport { 
%%                  Istring protocol_name; 
%%                  ProtocolAddressList addresses; %% eq a list of strings.
%%              }; 
%% Description: 
%%----------------------------------------------------------------------
'_get_protocols_supported'(_OE_This, State) ->
    {reply, ?get_Protocols(State), State}.

%%----------------------------------------------------------------------
%% Function   : set_directory
%% Arguments  : Directory - CosFileTransfer::Directory
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
set_directory(_OE_This, State, Directory)  when ?is_FTP(State); ?is_NATIVE(State) ->
    Mod  = ?get_Module(State),
    Path = filename:join('CosFileTransfer_Directory':
			 '_get_complete_file_name'(Directory)),
    case catch Mod:cd(?get_Server(State), Path) of
	ok ->
	    {reply, ok, ?set_CurrentDir(State, Path)};
	{error, epath} ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="Directory not found."});
	{error, elogin} ->
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not loggen in."});
	{error, econn} ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."});
	_ ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason = "Unexpected error."})
    end.

%%----------------------------------------------------------------------
%% Function   : create_file
%% Arguments  : FileNameList
%% Returns    : File
%% Description: This operation creates a File Object representing a 
%%              file which may or may not exist. Typically used as
%%              argument when invoking transfer/3. See also get_file/2.
%%----------------------------------------------------------------------
create_file(OE_This, State, FileNameList) ->
    {reply, cosFileTransferApp:create_file(OE_This, FileNameList), State}.

%%----------------------------------------------------------------------
%% Function   : create_directory
%% Arguments  : FileNameList - full path name.
%% Returns    : Directory
%% Description: 
%%----------------------------------------------------------------------
create_directory(OE_This, State, FileNameList) when ?is_FTP(State); 
						    ?is_NATIVE(State) ->
    Mod = ?get_Module(State),
    case Mod:mkdir(?get_Server(State), filename:join(FileNameList)) of
	ok ->
	    {reply, cosFileTransferApp:create_dir(OE_This, FileNameList), State};
	{error, epath} ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="Directory not found."});
	{error, elogin} ->
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not loggen in."});
	{error, econn} ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."});
	 _ ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Unknown error."})
    end.


%%----------------------------------------------------------------------
%% Function   : get_file
%% Arguments  : FileNameList
%% Returns    : FileWrapper
%% Description: This operation should be independent of the working Directory,
%%              i.e., a full path name must be supplied. The file or 
%%              directory the returned object is supposed to represent
%%              MUST(!!!!) exist.
%%----------------------------------------------------------------------
get_file(OE_This, State, FileNameList) when ?is_FTP(State); 
					    ?is_NATIVE(State) ->
    case check_type(OE_This, State, filename:join(FileNameList)) of
	{ndirectory, _Listing} ->
	    {reply, 
	     #'CosFileTransfer_FileWrapper'{the_file = 
					    cosFileTransferApp:
					    create_dir(OE_This, 
						       FileNameList),
					    file_type = ndirectory}, 
	     State};
	nfile ->
	    {reply, 
	     #'CosFileTransfer_FileWrapper'{the_file = 
					    cosFileTransferApp:
					    create_file(OE_This,
							FileNameList), 
					    file_type = nfile}, 
	     State};
	Other ->
	    %% If we want to return {stop, ....}
	    Other
    end.

%%----------------------------------------------------------------------
%% Function   : delete
%% Arguments  : File
%% Returns    : -
%% Description: 
%%----------------------------------------------------------------------
delete(_OE_This, State, File) when ?is_FTP(State); ?is_NATIVE(State) ->
    Mod = ?get_Module(State),
    Result =
	case 'CosPropertyService_PropertySet':
	    get_property_value(File, "is_directory") of
	    #any{value=false} ->
		Mod:delete(?get_Server(State), 
			   filename:join('CosFileTransfer_File':
					 '_get_complete_file_name'(File)));
	    #any{value=true} ->
		Mod:rmdir(?get_Server(State),
			  filename:join('CosFileTransfer_File':
					'_get_complete_file_name'(File)));
	    Other ->
		Other
	end,
    case Result of
	ok ->
	    {reply, ok, State};
	{error, epath} ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="File or Directory not found."});
	{error, elogin} ->
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not loggen in."});
	{error, econn} ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."});
	_ ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Unknown error."})
    end.

%%----------------------------------------------------------------------
%% Function   : transfer
%% Arguments  : SrcFile eq DestFile eq CosFileTransfer::File
%% Returns    : -
%% Description: DestFile must be a newly created File object, using create_file()
%%              on the Target FileTransferSession, prior to calling transfer().
%%----------------------------------------------------------------------
transfer(OE_This, State, SrcFile, DestFile) when ?is_ORBER_NATIVE(State) ->
    case which_FTS_type(OE_This, SrcFile, DestFile) of
	{source, TargetFTS} ->
	    %% The source FTS is supposed to be the active one, set up a connection.
	    Protocols = 'CosFileTransfer_FileTransferSession':
		        '_get_protocols_supported'(TargetFTS),
	    SrcName  = 'CosFileTransfer_File':'_get_complete_file_name'(SrcFile),
	    Pid = spawn(?MODULE, invoke_call, [self(), transfer, 
					       [TargetFTS, SrcFile, DestFile]]), 
	    send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
		      filename:join(SrcName)),
	    check_reply(Pid),
	    {reply, ok, State};
	{target, _SourceFTS} ->
	    DestName = 'CosFileTransfer_File':'_get_complete_file_name'(DestFile),
	    receive_file(?get_MyType(State), ?get_Connection(State), 
			 ?get_ConnectionTimeout(State),
			 filename:join(DestName), write),
	    {reply, ok, State}
    end;
transfer(OE_This, State, SrcFile, DestFile) when ?is_FTP(State); ?is_NATIVE(State) ->
    case which_FTS_type(OE_This, SrcFile, DestFile) of
	{source, TargetFTS} ->
	    source_FTS_operation(State, SrcFile, DestFile, transfer, 0, TargetFTS);
	{target, _SourceFTS} ->
	    target_FTS_operation(State, SrcFile, DestFile, send, 0)
    end.


%%----------------------------------------------------------------------
%% Function   : append
%% Arguments  : SrcFile eq DestFile eq CosFileTransfer::File
%% Returns    : -
%% Description: 
%%----------------------------------------------------------------------
append(OE_This, State, SrcFile, DestFile) when ?is_ORBER_NATIVE(State) ->
    case which_FTS_type(OE_This, SrcFile, DestFile) of
	{source, TargetFTS} ->
	    SrcName  = filename:join('CosFileTransfer_File':
				     '_get_complete_file_name'(SrcFile)),
	    check_type(OE_This, State, SrcName),
	    %% The source FTS is supposed to be the active one, set up a connection.
	    Protocols = 'CosFileTransfer_FileTransferSession':
		        '_get_protocols_supported'(TargetFTS),
	    Pid = spawn(?MODULE, invoke_call, [self(), append, 
					       [TargetFTS, SrcFile, DestFile]]), 
	    send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
		      SrcName),
	    check_reply(Pid),
	    {reply, ok, State};
	{target, _SourceFTS} ->
	    DestName = filename:join('CosFileTransfer_File':
				     '_get_complete_file_name'(DestFile)),
	    check_type(OE_This, State, DestName),
	    receive_file(?get_MyType(State), ?get_Connection(State),
			 ?get_ConnectionTimeout(State), DestName, append),
	    {reply, ok, State}
    end;
append(OE_This, State, SrcFile, DestFile) when ?is_NATIVE(State) ->
    case which_FTS_type(OE_This, SrcFile, DestFile) of
	{source, TargetFTS} ->
	    source_FTS_operation(State, SrcFile, DestFile, append, 0, TargetFTS);
	{target, _SourceFTS} ->
	    target_FTS_operation(State, SrcFile, DestFile, append, 0)
    end;
append(_OE_This, _State, _SrcFile, _DestFile) ->
    corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_NO}).


%%----------------------------------------------------------------------
%% Function   : insert
%% Arguments  : SrcFile eq DestFile eq CosFileTransfer::File
%%              Offset - long
%% Returns    : -
%% Description: 
%%----------------------------------------------------------------------
insert(OE_This, State, SrcFile, DestFile, Offset) when ?is_NATIVE(State) ->
    case which_FTS_type(OE_This, SrcFile, DestFile) of
	{source, TargetFTS} when ?is_ORBER_NATIVE(State) ->
	    SrcName  = 'CosFileTransfer_File':'_get_complete_file_name'(SrcFile),
	    check_type(OE_This, State, filename:join(SrcName)),
	    %% The source FTS is supposed to be the active one, set up a connection.
	    Protocols = 'CosFileTransfer_FileTransferSession':
		'_get_protocols_supported'(TargetFTS),
	    Pid = spawn(?MODULE, invoke_call, [self(), insert, 
					       [TargetFTS, SrcFile, 
						DestFile, Offset]]),
	    send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
		      filename:join(SrcName)),
	    check_reply(Pid),
	    {reply, ok, State};
	{source, TargetFTS} ->
	    source_FTS_operation(State, SrcFile, DestFile, insert, Offset, TargetFTS);
	{target, _SourceFTS} ->
	    target_FTS_operation(State, SrcFile, DestFile, insert, Offset)
    end;
insert(_OE_This, _State, _SrcFile, _DestFile, _Offset) ->
    corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_NO}).


%%----------------------------------------------------------------------
%% Function   : logout
%% Arguments  : -
%% Returns    : -
%% Description: 
%%----------------------------------------------------------------------
logout(_OE_This, State) when ?is_FTP(State); ?is_NATIVE(State) ->
    Mod = ?get_Module(State),
    catch Mod:close(?get_Server(State)),
    {stop, normal, ok, State}.

%%======================================================================
%% Internal functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function   : oe_orber_create_directory_current
%% Arguments  : -
%% Returns    : Directory
%% Description: Creates a Directory describing the working directory
%%              of the remote server, e.g., an FTP-server.
%%----------------------------------------------------------------------
oe_orber_create_directory_current(OE_This, State)  when ?is_FTP(State); 
							?is_NATIVE(State) ->
    Mod = ?get_Module(State),
    FileNameList = filename:split(?get_CurrentDir(State)),
    case Mod:nlist(?get_Server(State), ?get_CurrentDir(State)) of
	{ok, _Listing} ->
	    {reply, cosFileTransferApp:create_dir(OE_This, FileNameList), 
	     State};
	{error, epath} ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="Directory not found."});
	{error, elogin} ->
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not loggen in."});
	{error, econn} ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."});
	 _ ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Unknown error."})
    end.
%%----------------------------------------------------------------------
%% Function   : oe_orber_get_content
%% Arguments  : -
%% Returns    : string
%% Description: 
%%----------------------------------------------------------------------
oe_orber_get_content(OE_This, State, FileNameList, Parent)  when ?is_FTP(State); 
								 ?is_NATIVE(State) ->
    Mod = ?get_Module(State),
    case Mod:nlist(?get_Server(State), filename:join(FileNameList)) of
	{ok, Listing} ->
	    create_content(Listing, OE_This, State, Parent, FileNameList);
	{error, epath} ->
	    {reply, [], State};
	_ ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="Directory not found."})
    end.

%%----------------------------------------------------------------------
%% Function   : oe_orber_count_children
%% Arguments  : -
%% Returns    : string
%% Description: 
%%----------------------------------------------------------------------
oe_orber_count_children(OE_This, State, FileNameList)  when ?is_FTP(State); 
							 ?is_NATIVE(State) ->
    case catch check_type(OE_This, State, filename:join(FileNameList)) of
	{ndirectory, Members} ->
	    {reply, length(Members), State};
	{stop, normal, _, _} ->
	    {stop, normal, 
	     {'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}}, 
	     State};
	_->
	    corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
    end.

%%----------------------------------------------------------------------
%% Function   : delete_tmp_file
%% Arguments  : -
%% Returns    : ok | {'EXCEPTION', E}
%% Description: 
%%----------------------------------------------------------------------
delete_tmp_file(TmpFileName, ErrorMsg) ->
    case file:delete(TmpFileName) of
	ok ->
	    ok;
	_ ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'{reason=ErrorMsg})
    end.


%%----------------------------------------------------------------------
%% Function   : invoke_call
%% Arguments  : -
%% Returns    : ok | {'EXCEPTION', E}
%% Description: 
%%----------------------------------------------------------------------
invoke_call(Pid, Op, Args) ->
    Result = (catch apply('CosFileTransfer_FileTransferSession', Op, Args)),
    Pid ! {transfer_result, self(), Result},
    ok.

%%----------------------------------------------------------------------
%% Function   : check_reply
%% Arguments  : Pid - the pid of the spawned process.
%% Returns    : ok | {'EXCEPTION', E}
%% Description: 
%%----------------------------------------------------------------------
check_reply(Pid) ->
    receive 
	{transfer_result, Pid, ok} ->
	    ok;
	{transfer_result, Pid, {'EXCEPTION', E}} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:check_reply();
Raised exception: ", [?LINE, E], ?DEBUG_LEVEL),
	    corba:raise(E);
	{transfer_result, Pid, {'EXIT', Reason}} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:check_reply();
Got EXIT-signal with reason: ", [?LINE, Reason], ?DEBUG_LEVEL),
	    corba:raise(#'INTERNAL'{minor=199, 
				    completion_status=?COMPLETED_NO})
    after infinity ->
	    %% Should we add an exception here or do we reuse the iiop_timeout?
	    %% For now keep as is.
	    corba:raise(#'INTERNAL'{minor=199, 
				    completion_status=?COMPLETED_NO})
    end.


%%----------------------------------------------------------------------
%% Function   : which_FTS_type
%% Arguments  : -
%% Returns    : {source, FTS} | {target, FTS} | {'EXCEPTION', #'BAD_PARAM'{}}
%% Description: Used to determine if the target FTS is supposed to act
%%              as sender or receiver and also return the counter part FTS. 
%%              An exception is raised if the user supplied incorrect parameters.
%%----------------------------------------------------------------------
which_FTS_type(OE_This, SrcFile, DestFile) ->
    TargetFTS = 'CosFileTransfer_File':'_get_associated_session'(DestFile),
    SourceFTS = 'CosFileTransfer_File':'_get_associated_session'(SrcFile),
    case corba_object:is_equivalent(OE_This, TargetFTS) of
	true ->
	    {target, SourceFTS};
	false ->	    
	    case corba_object:is_equivalent(OE_This, SourceFTS) of
		true ->
		    {source, TargetFTS};
		false ->
		  corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO})
	    end
    end.


%%----------------------------------------------------------------------
%% Function   : setup_connection
%% Arguments  : A list of #'CosFileTransfer_ProtocolSupport'{}
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
setup_connection([], Protocol, _) ->
    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:setup_connection(~p);
The Protocols listed are not supported.", [?LINE, Protocol], ?DEBUG_LEVEL),
    corba:raise(#'CosFileTransfer_TransferException'{reason="Unsupported protocol"});
setup_connection([#'CosFileTransfer_ProtocolSupport'{protocol_name="TCP/IP", 
						     addresses=Addr}|_], 
		 tcp, Timeout) ->
    setup_connection_helper(Addr, gen_tcp, [], Timeout);
setup_connection([#'CosFileTransfer_ProtocolSupport'{protocol_name="SSL", 
						     addresses=Addr}|_], 
		 ssl, Timeout) ->
    Options = [{certfile, cosFileTransferApp:ssl_client_certfile()},
	       {verify, cosFileTransferApp:ssl_client_verify()},
	       {depth, cosFileTransferApp:ssl_client_depth()}] ++ 
	ssl_client_cacertfile_option(),
    setup_connection_helper(Addr, ssl, Options, Timeout);
setup_connection([_|T], Type, Timeout) ->
    setup_connection(T, Type, Timeout).

setup_connection_helper([], _, _, _) ->
    corba:raise(#'CosFileTransfer_RequestFailureException'
		{reason="Unable to contact remote server."});
setup_connection_helper([H|T], Driver, Options, Timeout) ->
    case string:tokens(H, ":") of
	[Host, Port] when Driver == gen_tcp ->
	    case gen_tcp:connect(Host, list_to_integer(Port), 
				 [binary, 
				  {packet, raw}, 
				  {reuseaddr, true}, 
				  {nodelay, true}|Options], Timeout) of
		{ok, Sock} ->
		    {gen_tcp, Sock};
		_->
		    %% No response.
		    setup_connection_helper(T, Driver, Options, Timeout)
	    end;
	[Host, Port] when Driver == ssl ->
	    case ssl:connect(Host, list_to_integer(Port), 
			     [binary,
			      {packet, 0},
			      {active, false}|Options], Timeout) of
		{ok, Sock} ->
		    {ssl, Sock};
		_->
		    %% No response.
		    setup_connection_helper(T, Driver, Options, Timeout)
	    end;
	_ ->
	    %% Badly configured address.
	    setup_connection_helper(T, Driver, Options, Timeout)
    end.

ssl_client_cacertfile_option() ->
    case cosFileTransferApp:ssl_client_cacertfile() of
	[] ->
	    [];
	X when list(X) ->
	    {cacertfile, X};
	_ ->
	    []
    end.

%%----------------------------------------------------------------------
%% Function   : create_content
%% Arguments  : 
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
create_content(Listing, OE_This, State, Parent, PathList) ->
    create_content(string:tokens(Listing, ?SEPARATOR), OE_This, 
		   State, Parent, PathList, []).

create_content([], _OE_This, State, _Parent, _PathList, Acc) ->
    {reply, Acc, State};
create_content([H|T], OE_This, State, Parent, PathList, Acc) ->
    FullPathList = PathList ++[filename:basename(H)],
    case check_type(OE_This, State, filename:join(FullPathList)) of
	nfile ->
	    create_content(T, OE_This, State, Parent, PathList, 
			   [#'CosFileTransfer_FileWrapper'
			    {the_file = cosFileTransferApp:create_file(OE_This, 
								       FullPathList, 
								       Parent),
			     file_type = nfile}|Acc]);
	{ndirectory, _Members} ->
	    create_content(T, OE_This, State, Parent, PathList, 
			   [#'CosFileTransfer_FileWrapper'
			    {the_file = cosFileTransferApp:create_dir(OE_This, 
								      FullPathList, 
								      Parent),
			     file_type = ndirectory}|Acc]);
	Other ->
	    Other
    end.
    

%%----------------------------------------------------------------------
%% Function   : MISC functions
%% Arguments  : 
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
setup_local(tcp) ->
    {ok,Socket}=gen_tcp:listen(0, [binary, 
				   {packet, 0},
				   {backlog,1},
				   {active, false}]),
    {ok, Port} = inet:port(Socket),
    {Socket, [#'CosFileTransfer_ProtocolSupport'{protocol_name="TCP/IP",
						 addresses = [local_address(Port)]}]};
setup_local(ssl) ->
    Options = [{certfile, cosFileTransferApp:ssl_server_certfile()},
	       {verify, cosFileTransferApp:ssl_server_verify()},
	       {depth, cosFileTransferApp:ssl_server_depth()}] ++ 
	ssl_server_cacertfile_option(),
    {ok,Socket}=ssl:listen(0, [binary, 
			       {packet, 0},
			       {backlog,1},
			       {active, false}|Options]),
    {ok, Port} = ssl:port(Socket),
    {Socket, [#'CosFileTransfer_ProtocolSupport'{protocol_name="SSL",
						 addresses = [local_address(Port)]}]}.

local_address(Port) ->
    {ok, Hostname} = inet:gethostname(),
    {ok, {A1, A2, A3, A4}} = inet:getaddr(Hostname, inet),
    integer_to_list(A1) ++ "." ++ integer_to_list(A2) ++ "." ++ integer_to_list(A3)
	++ "." ++ integer_to_list(A4)++":"++integer_to_list(Port).

ssl_server_cacertfile_option() ->
    case cosFileTransferApp:ssl_server_cacertfile() of
	[] ->
	    [];
	X when list(X) ->
	    [{cacertfile, X}];
	_ ->
	    []
    end.

%%----------------------------------------------------------------------
%% Function   : source_file_operation
%% Arguments  : 
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
source_FTS_operation(State, SrcFile, DestFile, Op, Offset, FTS) ->
    Mod = ?get_Module(State),
    %% The source FTS is supposed to be the active one, set up a connection.
    Protocols = 'CosFileTransfer_FileTransferSession':'_get_protocols_supported'(FTS),
    SrcName  = 'CosFileTransfer_File':'_get_complete_file_name'(SrcFile),
    TempName = cosFileTransferApp:create_name("TemporarySrcFile"),
    case Mod:recv(?get_Server(State),  filename:join(SrcName), TempName) of
	ok when Op == insert ->
            %% Downloaded the File, we are now ready to transmit.
	    Pid = spawn(?MODULE, invoke_call, [self(), insert, 
					       [FTS, SrcFile, DestFile, Offset]]),
	    send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
		      TempName),
            %% Delete the temporary local copy.
	    delete_tmp_file(TempName, 
			    "Transfer completed but failed to remove temporary local copy."),
	    check_reply(Pid),
	    {reply, ok, State};
	ok ->
            %% Downloaded the File, we are now ready to transmit.
	    Pid = spawn(?MODULE, invoke_call, [self(), Op, [FTS, SrcFile, DestFile]]),
	    send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
		      TempName),
	    %% Delete the temporary local copy.
	    delete_tmp_file(TempName, 
			    "Transfer completed but failed to remove temporary local copy."),
	    check_reply(Pid),
	    {reply, ok, State};
	{error, epath} ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="File not found."});
	{error, elogin} ->
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not loggen in."});
	{error, econn} ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."})
    end.
  
%%----------------------------------------------------------------------
%% Function   : target_file_operation
%% Arguments  : 
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
target_FTS_operation(State, _SrcFile, DestFile, Op, Offset) ->
    Mod = ?get_Module(State),
    DestName = 'CosFileTransfer_File':'_get_complete_file_name'(DestFile),
    TempName = cosFileTransferApp:create_name("TemporaryDestFile"),
    receive_file(?get_MyType(State), ?get_Connection(State), 
		 ?get_ConnectionTimeout(State), TempName, write),
    Result =
    if
	Op == insert ->
	    Mod:insert(?get_Server(State), TempName, filename:join(DestName), Offset);
	true ->
	    Mod:Op(?get_Server(State), TempName, filename:join(DestName))
    end,
    case Result of
	ok ->
            %% Delete the temporary local copy.
	    delete_tmp_file(TempName, 
			    "Transfer completed but failed to remove temporary local copy."),
            %% Completed the transfer succesfully.
	    {reply, ok, State};
	{error, epath} ->
	    delete_tmp_file(TempName,
			    "IllegalOperationException and not able to remove temporary local copy."),
	    corba:raise(#'CosFileTransfer_IllegalOperationException'
			{reason="Not allowed by destination."});
	{error, elogin} ->
	    delete_tmp_file(TempName,
			    "SessionException and not able to remove temporary local copy."),
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not logged in."});
	{error, econn} ->
	    delete_tmp_file(TempName,
			    "TransferException and not able to remove temporary local copy."),
	    corba:raise(#'CosFileTransfer_TransferException'
			{reason="Premature connection ending."});
		{error, etnospc} ->
	    delete_tmp_file(TempName,
			    "RequestFailureException and not able to remove temporary local copy."),
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."});
	{error, efnamena} ->
	    delete_tmp_file(TempName,
			    "IllegalOperationException and not able to remove temporary local copy."),
	    corba:raise(#'CosFileTransfer_IllegalOperationException'
			{reason="Not allowed by destination."})
    end.

%%----------------------------------------------------------------------
%% Function   : receive_file
%% Arguments  : Driver   - currently only gen_tcp supported.
%%              LSocket  - which socket to use.
%%              FileName - an absolute file name representing the
%%                         file we want to create or append to.
%%              Type     - 'read', 'write', 'append'.
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
receive_file(tcp, LSock, Timeout, FileName, Type) ->
    %% The Type can be the ones allowed by the file-module, i.e., 
    %% 'read', 'write' or 'append'
    FD = file_open(FileName, Type),
    case gen_tcp:accept(LSock, Timeout) of
	{ok, Sock} ->
	    receive_file_helper(gen_tcp, Sock, FD);
	{error, timeout} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:receive_file();
gen_tcp:accept(~p) timed out", [?LINE, Timeout], ?DEBUG_LEVEL),
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="TCP accept timed out.."});
	{error, Why} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:receive_file();
gen_tcp:accept(~p) failed: ~p", [?LINE, Timeout, Why], ?DEBUG_LEVEL),
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="TCP accept failed."})
    end;
receive_file(ssl, LSock, Timeout, FileName, Type) ->
    %% The Type can be the ones allowed by the file-module, i.e., 
    %% 'read', 'write' or 'append'
    FD = file_open(FileName, Type),
    case ssl:accept(LSock, Timeout) of
	{ok, Sock} ->
	    receive_file_helper(ssl, Sock, FD);
	{error, timeout} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:receive_file();
ssl:accept(~p) timed out", [?LINE, Timeout], ?DEBUG_LEVEL),
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="TCP accept timed out.."});
	{error, Why} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:receive_file();
ssl:accept(~p) failed: ~p", [?LINE, Timeout, Why], ?DEBUG_LEVEL),
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="TCP accept failed."})
    end.

receive_file_helper(Driver, Sock, FD) ->
    case Driver:recv(Sock, 0) of
	{ok, Bin} ->
	    file:write(FD, Bin),
	    receive_file_helper(Driver, Sock, FD);
	{error, closed} ->
	    file:close(FD);
	What ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:receive_file(~p);
Error occured when receiving data: ~p", [?LINE, Driver, What], ?DEBUG_LEVEL),
	    corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
    end.

%%----------------------------------------------------------------------
%% Function   : send_file
%% Arguments  : Driver   - currently only gen_tcp supported.
%%              Sock     - which socket to use.
%%              FileName - an absolute file name representing the
%%                         file we want to send.
%% Returns    : 
%% Description: 
%%----------------------------------------------------------------------
send_file(Protocols, Type, Timeout, FileName) ->
    {Driver, Sock} = setup_connection(Protocols, Type, Timeout),
    FD = file_open(FileName, read),
    BuffSize = cosFileTransferApp:get_buffert_size(),
    send_file_helper(Driver, Sock, FD, BuffSize).

send_file_helper(Driver, Sock, FD, BuffSize) ->
    case file:read(FD, BuffSize) of
	eof ->
	    file:close(FD),
	    Driver:close(Sock);
	{ok, Bin} ->
	    case Driver:send(Sock, Bin) of
		ok ->
		    send_file_helper(Driver, Sock, FD, BuffSize);
		What ->
		    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:send_file_helper(~p);
Error occured when sending data: ~p", [?LINE, Driver, What], ?DEBUG_LEVEL),
		    corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
	    end
    end.


file_open(File, Type) ->
    case file:open(File, [raw, binary, Type]) of
	{ok, FD} ->
	    FD;
	{error, What} ->
	    orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:file_open(~p);
Failed to open the file due to: ~p", [?LINE, File, What], ?DEBUG_LEVEL),
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Unable to open given file."})
    end.

%%----------------------------------------------------------------------
%% Function   : check_type
%% Arguments  : FullName - an absolute file name representing the
%%                         file or directory we want to evaluate.
%% Returns    : 
%% Description: 
%% When communcating with FTP-servers on different platforms a variety of
%% answers can be returned. A few examples:
%%
%% ### ftp:nlist on an empty directory ###
%% {ok, ""}, {error, epath}
%% 
%% ### ftp:nlist on a non-existing directory or file ###
%% {ok, "XXX: No such file or directory}, {error, epath}
%%
%% ### ftp:nlist on an existing directory with one contained item ###
%% {ok, "Item"}
%%
%% Comparing the above we see that it's virtually impossible to tell apart
%% {ok, "XXX: No such file or directory} and {ok, "Item"}.
%% Hence, it's easier to test if it's possible to do ftp:cd instead.
%% Ugly, but rather effective. If we look at the bright side, it's only
%% necessary when we try to lookup:
%% * non-existing item
%% * A directory with one member only.
%% * An empty directory.
%% 
%% Furthermore, no need for traversing Listings etc.
%%----------------------------------------------------------------------
check_type(_OE_This, State, FullName) when ?is_FTP(State); ?is_NATIVE(State) ->
    Mod = ?get_Module(State),
    Result =
	case Mod:nlist(?get_Server(State), FullName) of
	    {ok, Listing} when length(Listing) > 0->
		case string:tokens(Listing, ?SEPARATOR) of
		    [FullName] ->
			nfile;
		    Members when length(Members) > 1 ->
			%% Must test if more than one member since sometimes
			%% this operation returns for example:
			%% {ok, "XXX No such file or drectory"}
			{ndirectory, Members};
		    Member ->
			case Mod:cd(?get_Server(State), FullName) of
			    ok ->
				case Mod:cd(?get_Server(State), 
					    ?get_CurrentDir(State)) of
				    ok ->
					{ndirectory, Member};
				    _ ->
                            		%% Failed, we cannot continue since the
					%% FTS now pointso an incorrect Directory.
					%% Hence, we must terminate.
					{stop, normal, 
					 {'EXCEPTION', 
					  #'CosFileTransfer_RequestFailureException'
					  {reason="Unknown error."}}, State}
				end;
			    {error, E} ->
				{error, E};	
			    _ ->
				nfile
			end
		end;
	    {error, epath} ->
		%% Might be a file.
		DirName = filename:dirname(FullName),
		case Mod:nlist(?get_Server(State), DirName) of
		    {ok,  Listing} when length(Listing) > 0->
			Members = string:tokens(Listing, ?SEPARATOR),
			case lists:member(FullName, Members) of
			    true ->
				nfile;
			    _ ->
				BName = filename:basename(FullName),
				case lists:member(BName, Members) of
				    true ->
					nfile;
				    _ ->
					{error, epath}
				end
			end;
		    _ ->
			{error, epath}
		end;
	    _ ->
		case Mod:cd(?get_Server(State), FullName) of
		    ok ->
			case Mod:cd(?get_Server(State), ?get_CurrentDir(State)) of
			    ok ->
				{ndirectory, []};
			    _ ->
                            	%% Failed, we cannot continue since the
				%% FTS now pointso an incorrect Directory.
				%% Hence, we must terminate.
				{stop, normal, 
				 {'EXCEPTION', 
				  #'CosFileTransfer_RequestFailureException'
				  {reason="Unknown error."}}, State}
			end;
		    _ ->
			{error, epath}
		end
	end,
    case Result of
	{error, epath} ->
	    corba:raise(#'CosFileTransfer_FileNotFoundException'
			{reason="File or Directory not found."});
	{error, elogin} ->
	    corba:raise(#'CosFileTransfer_SessionException'
			{reason="User not logged in."});
	{error, econn} ->
	    corba:raise(#'CosFileTransfer_RequestFailureException'
			{reason="Premature connection ending."});
	Other ->
	    Other
    end.



%%======================================================================
%% END OF MODULE
%%======================================================================
