#include "ftpprot.h"

#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <iostream.h>
#include <pthread.h>

#if HAVE_DIRENT_H
#include <dirent.h>
#elif HAVE_NDIR_H
#include <ndir.h>
#elif HAVE_SYS_NDIR_H
#include <sys/ndir.h>
#elif HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif

#include <kapp.h>

void trailing(QString& str, char c)
{ if (str[str.length()-1] != c) str += c;}

void checkFD(int& fd)
{ if (fd != -1) { ::close(fd); fd = -1;}}

FtpProtocol::FtpProtocol(const char *host, const char *path, const char *login, const char *passwd, const char *port)
	: Protocol(Protocol::Ftp),Host(host),Path(path),Login(login),Passwd(passwd)
{
	QString	tmp(port);
	if (!tmp.isEmpty()) Port = tmp.toInt();
	else Port = -1;
	sock = dataSock = file = -1;
	CurrentPath = path;
	entries = new FileInfoList;
	entries->setAutoDelete(TRUE);
	rawEntries = new FileInfoList;
	rawEntries->setAutoDelete(TRUE);
	namedEntries = new QStrList(TRUE);
	namedEntries->setAutoDelete(TRUE);
	updateFlag = filterFlag = TRUE;
	flushBuffer();
	Prefix = "ftp://";
	dialog = 0;
	UnixType = FALSE;
}

FtpProtocol::~FtpProtocol()
{
	finish();
	delete rawEntries;
	delete entries;
	delete namedEntries;
}

FileInfo* FtpProtocol::getInfo(KURL& url)
{
	setPath(url.directory());
	updateEntries();
	FileInfoListIterator	it(*rawEntries);
	for (;it.current();++it) if (url.url() == it.current()->absFilePath()) break;
	if (it.current() == 0) return 0;
	FileInfo	*fi = new FileInfo(*(it.current()));
	return fi;
}

bool FtpProtocol::fillBuffer()
{
	if (sock != -1) {
		char	buf[1025];
		int	n;
		while ((n = ::read(sock,buf,1024)) > 0) {
			buf[n] = '\0';
			Buffer += buf;
		}
		if (n == 0 || errno == EAGAIN) return TRUE;
		else {				// communication was broken
			::close(sock);
			sock = -1;
			return FALSE;
		}
	}
	else return FALSE;
}

void FtpProtocol::readDataBuffer()
{
	if (dataSock != -1) {
		char	buf[1025];
		int	n;
		bool	done(FALSE);
		while (!done) {
			errno = 0;
			while ((n = ::read(dataSock,buf,1024)) > 0) {
				buf[n] = '\0';
				DataBuffer += buf;
			}
			if (errno != EAGAIN) done = TRUE;
		}
		::close(dataSock);
		dataSock = -1;
	}
}

void FtpProtocol::download()
{
	if (dataSock != -1) {
		char	buf[16384];
		int	n,w;
		bool	done(FALSE);
		while (!done) {
			errno = 0;
			while ((n = ::read(dataSock,buf,16383)) > 0) {
				buf[n] = '\0';
				w = ::write(file,buf,n);
				if (w != n) break;
				downBytes += n;
				if (dialog)
					if (totalBytes) dialog->setProgress((downBytes*100)/totalBytes);
					else dialog->setAbsoluteValue(downBytes);
			}
			if (errno != EAGAIN) done = TRUE;
		}
		::close(dataSock);
		dataSock = -1;
		::close(file);
		file = -1;
	}
}

void FtpProtocol::upload()
{
	if (dataSock != -1) {
		fcntl(dataSock,F_SETFL,(fcntl(dataSock,F_GETFL) & ~O_NONBLOCK));
		char	buf[16384];
		int	n,w;
		bool	done(FALSE);
		while (!done) {
			errno = 0;
			while ((n = ::read(file,buf,16383)) > 0) {
				buf[n] = '\0';
				w = ::write(dataSock,buf,n);
				if (w != n) break;
				downBytes += n;
				if (dialog) dialog->setProgress((downBytes*100)/totalBytes);
			}
			if (errno != EAGAIN) done = TRUE;
		}
		::close(dataSock);
		dataSock = -1;
		::close(file);
		file = -1;
	}
}

void FtpProtocol::flushBuffer()
{
	if (Aborted && sock != -1) fillBuffer();
	Aborted = FALSE;
	Buffer.truncate(0);
	posBuffer = 0;
}

QString FtpProtocol::readLine()
{
	int	pos;
	while ((pos = Buffer.find('\n',posBuffer)) == -1) {
		if (!fillBuffer()) break;
		else usleep(200000);
		pthread_testcancel();
	}
	if (pos == -1) return "";
	QString	retval = Buffer.mid(posBuffer,pos-posBuffer+1);
	posBuffer = pos+1;
	return retval;
}

int FtpProtocol::readAnswer()
{
//	flushBuffer();
	AnswerBuffer = "";
	QString	line = readLine();
	if (line.isEmpty()) return 0;
	while (!isdigit(line[0])) {
		line = readLine();
		if (line.isEmpty()) return 0;
	}
	AnswerBuffer += line;
	QString	matchNumber = line.mid(0,3);
	bool	multi = (line[3] == '-');
	if (multi) {
		bool	finished(FALSE);
		while (!finished) {
			line = readLine();
			if (line.isEmpty()) return 0;
			AnswerBuffer += line;
			if (isdigit(line[0]) && line[3] != '-') finished = TRUE;
		}
	}
cout << "Answer :\n" << AnswerBuffer.data() << endl;
	return matchNumber.toInt();
}

void FtpProtocol::sendCommand(const char *cmd, const char *args)
{
	flushBuffer();
	if (sock != -1) {
		QString	Cmd(cmd);
		if (args) {
			Cmd += " ";
			Cmd += args;
		}
		Cmd += "\r\n";
cout << "Command :\n" << Cmd.data() << endl;
		::write(sock,Cmd.data(),Cmd.length());
	}
}

void FtpProtocol::connectionClosed()
{
	if (sock != -1) {
		::close(sock);
		sock = -1;
	}
}

int FtpProtocol::setErrorMsg(int result)
{
	int	val(0);
	switch (result) {
	   case 0: connectionClosed(); ErrorMsg = i18n("Communication broken"); val = FTP_BROKEN; break;
	   case 4: ErrorMsg = i18n("Communication error.\nMessage from server :\n"); ErrorMsg += AnswerBuffer; val = FTP_COMMERROR; break;
	   case 5: ErrorMsg = i18n("FTP error.\nMessage from server :\n"); ErrorMsg += AnswerBuffer; val = FTP_ERROR; break;
	}
	return val;
}

int FtpProtocol::ftpConnect()
{
	sock = ::socket(PF_INET,SOCK_STREAM,0);
	if (sock < 0) {
cout << "can't create socket" << endl;
		ErrorMsg = i18n("Error while trying to connect");
		sock = -1;
		return FTP_CONNERROR;
	}
	struct hostent	*hostinfo = gethostbyname(Host.data());
	struct sockaddr_in	sin;
	if (!hostinfo) {
		ErrorMsg = i18n("Unknown host : ");
		ErrorMsg += Host.data();
		connectionClosed();
		return FTP_CONNERROR;
	}
	sin.sin_family = AF_INET;
	if (Port < 0) {
		struct servent	*pse = getservbyname("ftp","tcp");
		sin.sin_port = htons(pse->s_port);
	}
	else sin.sin_port = htons(Port);
	sin.sin_addr = *(struct in_addr*)hostinfo->h_addr;
	fcntl(sock,F_SETFL,(fcntl(sock,F_GETFL)|O_NONBLOCK));
//	errno = 0;
	if (::connect(sock,(struct sockaddr*)(&sin),sizeof(sin)) == -1) {
		if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
cout << "can't connect socket" << endl << "error : " << errno << endl;
			ErrorMsg = i18n("Error while trying to connect");
			connectionClosed();
			return FTP_CONNERROR;
		}
	}
	else {
//		fcntl(sock,F_SETFL,(fcntl(sock,F_GETFL)&~O_NONBLOCK));
		return FTP_OK;
	}
	fd_set	rd, wr;
	struct timeval	timeout;
	int	ret(0), n(120);
	FD_ZERO(&rd);
	FD_ZERO(&wr);
	FD_SET(sock,&rd);
	FD_SET(sock,&wr);
	while (n--) {
cout << "waiting : " << n << endl;
		timeout.tv_usec = 0;
		timeout.tv_sec = 1;
#ifdef HPUX
		ret = select((size_t)FD_SETSIZE,(int*)&rd,(int*)&wr,(int*)0,(const struct timeval*)&timeout);
#else
		ret = select(getdtablesize(),(fd_set*)&rd,(fd_set*)&wr,(fd_set*)0,(struct timeval*)&timeout);
#endif
		if (ret) {
//			fcntl(sock,F_SETFL,(fcntl(sock,F_GETFL)&~O_NONBLOCK));
			return FTP_OK;
		}
		pthread_testcancel();
	}
	ErrorMsg = i18n("Timeout connecting socket");
	connectionClosed();
	return FTP_CONNERROR;
}

int FtpProtocol::ftpLogin()
{
	if (sock == -1) return FTP_NOTCONN;
	QString		login;
	QString		passwd;
	if (Login.isEmpty()) login = "anonymous";
	else login = Login.data();
	if (Passwd.isEmpty()) {
		passwd = getlogin();
		passwd += '@';
		char	buf[1024];
		gethostname(buf,1024);
		passwd += buf;
	}
	else passwd = Passwd.data();
	sendCommand("USER",login.data());
	int	result = readAnswer();
	if (result == 220) result = (readAnswer()/100);		// skip welcome message
	else result = result/100;
	if (result == 3) {
		sendCommand("PASS",passwd.data());
		result = (readAnswer() / 100);
	}
	if (result != 2) { connectionClosed(); return setErrorMsg(result);}
	sendCommand("SYST");
	if ((result = (readAnswer()/100)) != 2) return setErrorMsg(result);
	if (strncmp(AnswerBuffer.data()+4,"UNIX",4) == 0) UnixType = TRUE;
	return FTP_OK;
}

int FtpProtocol::ftpPwd()
{
	if (sock == -1) return FTP_NOTCONN;
	sendCommand("PWD");
	int	result = (readAnswer()/100);
	if (result != 2) return setErrorMsg(result);
	int	pos1, pos2;
	pos1 = AnswerBuffer.find('"');
	pos2 = AnswerBuffer.find('"',pos1+1);
	if (pos1 >= 0 && pos2 >= 0) CurrentPath = AnswerBuffer.mid(pos1+1,pos2-pos1-1);
	trailing(CurrentPath,'/');
	return FTP_OK;
}

int FtpProtocol::ftpCd(const char *path)
{
	if (*path != '/') return FTP_ERROR;	// can handle only absolute path
	sendCommand("CWD",path);
	int	result = (readAnswer()/100);
	if (result != 2) return setErrorMsg(result);
	return ftpPwd();
}

int FtpProtocol::ftpList()
{
	DataBuffer.truncate(0);
	if (sock == -1) return FTP_NOTCONN;
	sendCommand("TYPE A");
	int	result = (readAnswer()/100);
	if (result != 2) return setErrorMsg(result);
	int	tmpSock = setupDataConnection();
	if (tmpSock < 0) return tmpSock;
	sendCommand("LIST",(UnixType ? "-laF" : 0));
	if ((result = (readAnswer()/100)) != 1) {
		::close(tmpSock);
		return setErrorMsg(result);
	}
	if (acceptDataConnection(tmpSock,FALSE) < 0) return FTP_SOCKERROR;
	readDataBuffer();
	if ((result = (readAnswer()/100)) != 2) return setErrorMsg(result);
	return FTP_OK;
}

int FtpProtocol::ftpMkDir(const char *dirName)
{
	if (sock == -1) return FTP_NOTCONN;
	sendCommand("MKD",dirName);
	int	result = (readAnswer()/100);
	if (result != 2) return setErrorMsg(result);
	return FTP_OK;
}

int FtpProtocol::ftpGet(const char *src, const char *dest)
{
	if (sock == -1) return FTP_NOTCONN;
	file = ::open(dest,O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
	if (file < 0) {
		ErrorMsg = i18n("Unable to open file ");
		ErrorMsg += dest;
		return FTP_ERROR;
	}
	sendCommand("TYPE I");
	int	result = (readAnswer()/100);
	if (result != 2) { checkFD(file); return setErrorMsg(result);}
	int	tmpSock = setupDataConnection();
	if (tmpSock < 0) { checkFD(file); return tmpSock;}
	sendCommand("RETR",src);
	if ((result = (readAnswer()/100)) != 1) {
		checkFD(file);
		::close(tmpSock);
		return setErrorMsg(result);
	}
	downBytes = 0;
	int	pos1 = AnswerBuffer.find("bytes"), pos2;
	if (pos1 != -1) {
		pos2 = pos1-2;
		char	*c = AnswerBuffer.data()+pos2;
		while (isdigit(*c)) c--, pos2--;
		totalBytes = AnswerBuffer.mid(pos2+1,pos1-pos2-2).toInt();
	}
	else totalBytes = 0;
/*	int	pos1 = AnswerBuffer.find('(');
	int	pos2 = AnswerBuffer.find(' ',pos1);
	totalBytes = AnswerBuffer.mid(pos1+1,pos2-pos1-1).toInt();*/
	if (acceptDataConnection(tmpSock,FALSE) < 0) { checkFD(file); return FTP_SOCKERROR;}
	if (dialog) {
		QString	msg = i18n("Downloading ");
		msg += (strrchr(src,'/')+1);
		QString	msg2(i18n("From "));
		msg2 += src;
		dialog->setLabelText(msg.data(),msg2.data());
	}
	download();
	if ((result = (readAnswer()/100)) != 2) return setErrorMsg(result);
	return FTP_OK;
}

int FtpProtocol::ftpGetDir(const char *src, const char *dest)
{
	if (sock == -1) return FTP_NOTCONN;
	updateFlag = filterFlag = TRUE;
	int	result(0);
	if ((result = ftpCd(src)) != FTP_OK) return result;
	QString	srcStr, destStr;
	if (::mkdir(dest,S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0) {
		ErrorMsg = i18n("Unable to create directory");
		ErrorMsg += " : ";
		ErrorMsg += dest;
		return FTP_ERROR;
	}
	if ((result = ftpList()) != FTP_OK) return result;
	char	*last, *c = strtok_r(DataBuffer.data(),"\n",&last);
	QStrList	list(TRUE);
	while (c) {
		list.append(c);
		c = strtok_r(0,"\n",&last);
	}
	QStrListIterator	it(list);
	bool			cont(TRUE);
	for (;it.current() && cont;++it) {
		if (strncmp(it.current(),"total",5) == 0) continue;
		FileInfo	*fi = new FileInfo(it.current(),(UnixType ? 1 : 2));
		if (fi->fileName() != "." && fi->fileName() != "..") {
			srcStr = (src + fi->fileName());
			destStr = (dest + fi->fileName());
			if (fi->isDir()) {
				trailing(srcStr,'/');
				trailing(destStr,'/');
				result = ftpGetDir(srcStr.data(),destStr.data());
			}
			else result = ftpGet(srcStr.data(),destStr.data());
			if (result != FTP_OK) cont = FALSE;
		}
		delete fi;
	}
	return result;
}

int FtpProtocol::ftpPut(const char *src, const char *dest)
{
	if (sock == -1) return FTP_NOTCONN;
	file = ::open(src,O_RDONLY);
	if (file < 0) {
		ErrorMsg = i18n("Unable to open file ");
		ErrorMsg += src;
		return FALSE;
	}
	struct stat	st;
	fstat(file,&st);
	sendCommand("TYPE I");
	int	result = (readAnswer()/100);
	if (result != 2) { checkFD(file); return setErrorMsg(result);}
	int	tmpSock = setupDataConnection();
	if (tmpSock < 0) { checkFD(file); return tmpSock;}
	sendCommand("STOR",dest);
	if ((result = (readAnswer()/100)) != 1) {
		checkFD(file);
		::close(tmpSock);
		return setErrorMsg(result);
	}
	downBytes = 0;
	totalBytes = st.st_size;
	if (acceptDataConnection(tmpSock,FALSE) < 0) { checkFD(file); return FTP_SOCKERROR;}
	if (dialog) {
		QString	msg = i18n("Uploading ");
		msg += (strrchr(dest,'/')+1);
		QString	msg2(i18n("From "));
		dialog->setLabelText(msg.data(),msg2.data());
	}
	upload();
	if ((result = (readAnswer()/100)) != 2) return setErrorMsg(result);
	return FTP_OK;
}

int FtpProtocol::ftpPutDir(const char *src, const char *dest)
{
	if (sock == -1) return FTP_NOTCONN;
	updateFlag = filterFlag = TRUE;
	DIR	*dir = opendir(src);
	QStrList	list;
	if (!dir) {
		ErrorMsg = i18n("Unable to open directory ");
		ErrorMsg += src;
		return FTP_ERROR;
	}
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,0);
	struct dirent	*file;
	while ((file = readdir(dir)) != 0) list.append(file->d_name);
	closedir(dir);
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
	int	result = ftpMkDir(src);
	if (result != FTP_OK) return result;
	QStrListIterator	it(list);
	QString		srcStr, destStr;
	bool		cont(TRUE);
	for (;it.current() && cont;++it) {
		if (strcmp(it.current(),".") != 0 && strcmp(it.current(),"..") != 0) {
			srcStr = (QString(src) + it.current());
			destStr = (QString(dest) + it.current());
			struct stat	st;
			stat(srcStr.data(),&st);
			if (S_ISDIR(st.st_mode)) {
				trailing(srcStr,'/');
				trailing(destStr,'/');
				result = ftpPutDir(srcStr.data(),destStr.data());
			}
			else result = ftpPut(srcStr.data(),destStr.data());
			if (result != FTP_OK) cont = FALSE;
		}
	}
	return result;
}

int FtpProtocol::ftpRename(const char *src, const char *dest)
{
	if (sock == -1) return FTP_NOTCONN;
	int	result(0);
	sendCommand("RNFR",src);
	if ((result = (readAnswer()/100)) != 3) return setErrorMsg(result);
	sendCommand("RNTO",dest);
	if ((result = (readAnswer()/100)) != 2) return setErrorMsg(result);
	return FTP_OK;
}

int FtpProtocol::ftpRm(const char *filename)
{
	if (sock == -1) return FTP_NOTCONN;
	int	result(0);
	sendCommand("DELE",filename);
	if ((result = (readAnswer()/100)) != FTP_OK) return setErrorMsg(result);
	return FTP_OK;
}

int FtpProtocol::ftpRmDir(const char *dirname)
{
	if (sock == -1) return FTP_NOTCONN;
	updateFlag = filterFlag = TRUE;
	int	result(0);
	if ((result = ftpCd(dirname)) != FTP_OK) return result;
	QString	srcStr;
	if ((result = ftpList()) != FTP_OK) return result;
	char	*last, *c = strtok_r(DataBuffer.data(),"\n",&last);
	QStrList	list(TRUE);
	while (c) {
		list.append(c);
		c = strtok_r(0,"\n",&last);
	}
	QStrListIterator	it(list);
	bool			cont(TRUE);
	for (;it.current() && cont;++it) {
		if (strncmp(it.current(),"total",5) == 0) continue;
		FileInfo	*fi = new FileInfo(it.current(),(UnixType ? 1 : 2));
		if (fi->fileName() != "." && fi->fileName() != "..") {
			srcStr = (dirname + fi->fileName());
			if (fi->isDir()) {
				trailing(srcStr,'/');
				result = ftpRmDir(srcStr.data());
			}
			else result = ftpRm(srcStr.data());
			if (result != FTP_OK) cont = FALSE;
		}
		delete fi;
	}
	if (result == FTP_OK) {
		sendCommand("RMD",dirname);
		result = (readAnswer()/100);
		if (result != 2) return setErrorMsg(result);
		return FTP_OK;
	}
	else return result;
}

bool FtpProtocol::init()
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_Init,this);
	launchThread(cmd);
	bool	retval = (cmd->done && (bool)cmd->out);
	th_ID = 0;
	delete cmd;
	return retval;
}

bool FtpProtocol::thread_Init()
{
	int	result(0);
	result = ftpConnect();
	if (result == FTP_OK) result = ftpLogin();
	if (result == FTP_OK) {
		if (CurrentPath.isEmpty()) result = ftpPwd();
		else if ((result = ftpCd(CurrentPath.data())) != FTP_OK) ftpPwd();
	}
	if (result != FTP_OK) return FALSE;
	if (!Login.isEmpty()) { Prefix += Login; Prefix += '@';}
	Prefix += Host;
	if (Port >= 0) { QString num; num.setNum(Port); Prefix += (':' + num);}
	Prefix += CurrentPath;
	return TRUE;
}

bool FtpProtocol::cleanup()
{
	if (sock != -1) {
		ErrorMsg = i18n("Operation aborted");
		sendCommand("ABOR");
cout << "waiting abort answer" << endl;
//		while (readAnswer()/10 != 22) ;
cout << "done" << endl;
//		connectionClosed();
		Aborted = TRUE;
	}
	if (file != -1) { ::close(file); file = -1;}
	if (dataSock != -1) { ::close(dataSock); dataSock = -1;}
	dialog = 0;
	return TRUE;
}

void FtpProtocol::shutDown()
{
cout << "forcing FTP shut down" << endl;
	ErrorMsg = i18n("FTP connection shut down");
	connectionClosed();
	if (file != -1) { ::close(file); file = -1;}
	if (dataSock != -1) { ::close(dataSock); dataSock = -1;}
	dialog = 0;
}

bool FtpProtocol::finish()
{
	if (sock != -1) {
		sendCommand("QUIT");
		readAnswer();
		connectionClosed();
	}
	if (file != -1) { ::close(file); file = -1;}
	if (dataSock != -1) { ::close(dataSock); dataSock = -1;}
	return TRUE;
}

void* FtpProtocol::thread_protocolCmd(ProtocolCmd *cmd)
{
	switch (cmd->code) {
	   case Prot_Init:
		cmd->out = (void*)thread_Init();
		break;
	   case Prot_Cd:
		cmd->out = (void*)thread_setPath((const char*)cmd->parg1);
		break;
	   case Prot_MkDir:
		cmd->out = (void*)thread_mkdir((const char*)cmd->parg1);
		break;
	   case Prot_List:
		cmd->out = (void*)thread_updateEntries();
		break;
	   case Prot_CopyFrom:
		cmd->out = (void*)thread_copyFromProtocol((const char*)cmd->parg1,(const char*)cmd->parg2,(bool)cmd->parg3,(ProgressDlg*)cmd->parg4);
		break;
	   case Prot_CopyTo:
		cmd->out = (void*)thread_copyToProtocol((const char*)cmd->parg1,(const char*)cmd->parg2,(bool)cmd->parg3,(ProgressDlg*)cmd->parg4);
		break;
	   case Prot_Rename:
		cmd->out = (void*)thread_rename((const char*)cmd->parg1,(const char*)cmd->parg2);
		break;
	   case Prot_Rm:
		cmd->out = (void*)thread_remove((const char*)cmd->parg1);
		break;
	   case Prot_RmDir:
		cmd->out = (void*)thread_rmdir((const char*)cmd->parg1,(ProgressDlg*)cmd->parg2);
		break;
	}

	cmd->done = TRUE;
	return 0;
}

void FtpProtocol::thread_protocolCleanup(ProtocolCmd *cmd)
{
	cmd->prot->cleanup();
	cmd->out = (void*)0;
	cmd->done = TRUE;
}

int FtpProtocol::setupDataConnection()
{
	int	sData;
	union {
		struct sockaddr sa;
		struct sockaddr_in in;
	} sin;
	struct linger lng = { 0, 0 };
	int	on = 1;
	int	result;
	int	s;
	s = sizeof(sin);
	if ((result = getsockname(sock,&sin.sa,&s)) < 0) return FTP_SOCKERROR;
	sData = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (sData < 0) return FTP_SOCKERROR;
	if (setsockopt(sData,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on)) == -1)
	{
		::close(sData);
		return FTP_SOCKERROR;
	}
	if (setsockopt(sData,SOL_SOCKET,SO_LINGER,(char*)&lng,sizeof(lng)) == -1)
	{
		::close(sData);
		return FTP_SOCKERROR;
	}
	sin.in.sin_port = 0;
	if (bind(sData,&sin.sa,sizeof(sin)) == -1)
	{
		::close(sData);
		return FTP_SOCKERROR;
	}
	if (listen(sData,1) < 0)
	{
		::close(sData);
		return FTP_SOCKERROR;
	}
	if ((result = getsockname(sData,&sin.sa,&s)) < 0) return FTP_SOCKERROR;
	QString		tmp;
	tmp.sprintf("PORT %d,%d,%d,%d,%d,%d",
		    (unsigned char)sin.sa.sa_data[2],(unsigned char)sin.sa.sa_data[3],
		    (unsigned char)sin.sa.sa_data[4],(unsigned char)sin.sa.sa_data[5],
		    (unsigned char)sin.sa.sa_data[0],(unsigned char)sin.sa.sa_data[1]);
	sendCommand(tmp.data());
	result = (readAnswer()/100);
	if (result != 2) {
		::close(sData);
		return setErrorMsg(result);
	}
	return sData;
}

int FtpProtocol::acceptDataConnection(int sData, bool writeFlag)
{
	union {
		struct sockaddr sa;
		struct sockaddr_in in;
	} newSin;
	int	s;
	s = sizeof(newSin);
	fcntl(sData,F_SETFL,(fcntl(sData,F_GETFL)|O_NONBLOCK));
	fd_set	rd, wr;
	struct timeval	timeout;
	int	ret(0), n(120);
	FD_ZERO(&rd);
	FD_ZERO(&wr);
	FD_SET(sData,&rd);
	FD_SET(sData,&wr);
	while (n--) {
cout << "waiting : " << n << endl;
		timeout.tv_usec = 0;
		timeout.tv_sec = 1;
#ifdef HPUX
		ret = select((size_t)FD_SETSIZE,(int*)&rd,(int*)&wr,(int*)0,(const struct timeval*)&timeout);
#else
		ret = select(getdtablesize(),(fd_set*)&rd,(fd_set*)&wr,(fd_set*)0,(struct timeval*)&timeout);
#endif
		if (ret) break;
		pthread_testcancel();
	}
	if ((dataSock = accept(sData,&newSin.sa,&s)) < 0) {
		::close(sData);
		return FTP_SOCKERROR;
	}
	fcntl(dataSock,O_NONBLOCK);
	::close(sData);
	return FTP_OK;
}

//------------------------------------------------------------------------------------------------

bool FtpProtocol::updateEntries()
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_List,this);
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	if (retval) updateFlag = FALSE;
	return retval;
}

bool FtpProtocol::thread_updateEntries()
{
	if (ftpList() != FTP_OK) return FALSE;
	rawEntries->clear();
	char	*last, *c = strtok_r(DataBuffer.data(),"\n",&last);
	QString	prefix("ftp://");
	if (!Login.isEmpty()) { prefix += Login; prefix += '@';}
	prefix += (Host + CurrentPath);
	while (c) {
		if (strncmp(c,"total",5) == 0) { c = strtok_r(0,"\n",&last); continue;}
		FileInfo	*fi = new FileInfo(c,(UnixType ? 1 : 2));
		QString		completeName = (prefix + fi->fileName());
		if (fi->isDir()) completeName += '/';
		fi->setPath(completeName.data());
		KMimeExtension	*ext = 0;
		if (fi->isFile()) {
			ext = getMimeTypeFromExtension(fi);
			if (ext == 0) ext = getMimeTypeFromName(fi);
//			if (ext == 0 && UseMagic) ext = getMimeTypeFromMagic(fi);
			if (ext != 0) fi->setDescription(ext->Description.data());
			else fi->setDescription(i18n("Unknown"));
		}
		else {
			ext = getMimeTypeFromNode(fi);
			if (ext != 0) fi->setDescription(ext->Description.data());
			else if (fi->isDir()) fi->setDescription(i18n("Directory"));
			else fi->setDescription(i18n("Unknown"));
		}
		fi->setMimeType(ext);
		rawEntries->append(fi);
		c = strtok_r(0,"\n",&last);
	}
	return TRUE;
}

bool FtpProtocol::updateFilteredEntries(bool hidden, bool dirsOnly, bool showArchive)
{
	entries->clear();
	FileInfoListIterator	it(*rawEntries);
	SubProtocol	*subProt;
	for (;it.current();++it) {
		if (it.current()->fileName() == "." || it.current()->fileName() == "..") continue;
		if (!hidden && it.current()->fileName()[0] == '.') continue;
		if (!it.current()->isDir() && dirsOnly && (!showArchive ||
		    (subProt=matchFileName(it.current()->fileName(),ProtList)) == 0))
			continue;
		FileInfo	*fi = new FileInfo(*(it.current()));
		if (!fi->isDir() && dirsOnly && showArchive) {
			QString		complete(fi->absFilePath().data());
			complete += '#';
			complete += subProt->protocol;
			complete += ':';
			fi->setPath(complete.data());
		}
		entries->append(fi);
		namedEntries->append(fi->fileName().data());
	}
	return TRUE;
}

const FileInfoList* FtpProtocol::entryInfoList(bool hidden, bool dirsOnly, bool showArchive)
{
	if (updateFlag && !updateEntries()) return 0;
	if (filterFlag && !updateFilteredEntries(hidden,dirsOnly,showArchive)) return 0;
	return entries;
}

const QStrList* FtpProtocol::entryList(bool hidden, bool dirsOnly, bool showArchive)
{
	if (updateFlag && !updateEntries()) return 0;
	if (filterFlag && !updateFilteredEntries(hidden,dirsOnly,showArchive)) return 0;
	return namedEntries;
}

QString FtpProtocol::dirName()
{
	if (isRoot()) return "";
	int	pos = CurrentPath.findRev('/',2);
	return CurrentPath.mid(pos+1,CurrentPath.length()-pos);
}

bool FtpProtocol::mkdir(const char *pathname)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_MkDir,this,(void*)pathname);
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	if (retval) updateFlag = filterFlag = TRUE;
	return retval;
}

bool FtpProtocol::thread_mkdir(const char *pathname)
{
	if (ftpMkDir(pathname) != FTP_OK) return FALSE;
	return TRUE;
}

bool FtpProtocol::setPath(const char *pathname)
{
	QString	tmp(pathname);
	trailing(tmp,'/');
	if (tmp == CurrentPath) return TRUE;
	ProtocolCmd *cmd = new ProtocolCmd(Prot_Cd,this,(void*)(tmp.data()));
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	if (retval) updateFlag = filterFlag = TRUE;
	return retval;
}

bool FtpProtocol::thread_setPath(const char *pathname)
{
	if (ftpCd(pathname) != FTP_OK) return FALSE;
	return TRUE;
}

bool FtpProtocol::remove(const char *filename)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_Rm,this,(void*)filename);
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	if (retval) updateFlag = filterFlag = TRUE;
	return retval;
}

bool FtpProtocol::thread_remove(const char *filename)
{
	if (ftpRm(filename) != FTP_OK) return FALSE;
	return TRUE;
}

bool FtpProtocol::rmdir(const char *dirname, ProgressDlg *dlg)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_RmDir,this,(void*)dirname,(void*)dlg);
	dialog = dlg;
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	dialog = 0;
	if (retval) updateFlag = filterFlag = TRUE;
	return retval;
}

bool FtpProtocol::thread_rmdir(const char *dirname, ProgressDlg *dlg)
{
	if (ftpRmDir(dirname) != FTP_OK) return FALSE;
	return TRUE;
}

bool FtpProtocol::rename(const char *src, const char *dest)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_Rename,this,(void*)src,(void*)dest);
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	if (retval) updateFlag = filterFlag = TRUE;
	return retval;
}

bool FtpProtocol::thread_rename(const char *src, const char *dest)
{
	if (ftpRename(src,dest) != FTP_OK) return FALSE;
	return TRUE;
}

bool FtpProtocol::exists(const char *filename)
{
	return FALSE;
}

bool FtpProtocol::chmod(const char *filename, int perm)
{
	return FALSE;
}

bool FtpProtocol::copyToProtocol(const char *filename, const char *targetDir, bool move, ProgressDlg *dlg)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_CopyTo,this,(void*)filename,(void*)targetDir,(void*)move,(void*)dlg);
	dialog = dlg;
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	dialog = 0;
	th_ID = 0;
	return retval;
}

bool FtpProtocol::thread_copyToProtocol(const char *filename, const char *targetDir, bool move, ProgressDlg *dlg)
{
	QString		remoteDir(targetDir);
	QString		fileName(filename);
	trailing(remoteDir,'/');
	remoteDir += fileName.right(fileName.length()-fileName.findRev('/',fileName.length()-2)-1);
	if (fileName[fileName.length()-1] == '/') return (ftpPutDir(fileName.data(),remoteDir.data()) != FTP_OK ? FALSE : TRUE);
	else return (ftpPut(fileName.data(),remoteDir.data()) != FTP_OK ? FALSE : TRUE);
}

bool FtpProtocol::copyFromProtocol(const char *filename, const char *targetDir, bool move, ProgressDlg *dlg)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_CopyFrom,this,(void*)filename,(void*)targetDir,(void*)move,(void*)dlg);
	dialog = dlg;
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	dialog = 0;
	th_ID = 0;
	return retval;
}

bool FtpProtocol::thread_copyFromProtocol(const char *filename, const char *targetDir, bool move, ProgressDlg *dlg)
{
	QString		localFile(filename);
	QString		remoteFile(targetDir);
	trailing(remoteFile,'/');
	remoteFile += localFile.right(localFile.length()-localFile.findRev('/',localFile.length()-2)-1);
	if (localFile[localFile.length()-1] == '/') return (ftpGetDir(localFile.data(),remoteFile.data()) != FTP_OK ? FALSE : TRUE);
	else return (ftpGet(localFile.data(),remoteFile.data()) != FTP_OK ? FALSE : TRUE);
}

bool FtpProtocol::matchURL(KURL url)
{
	bool	retval = TRUE;
	if (url.hasSubProtocol()) retval = FALSE;
	else if (strncmp(url.protocol(),"ftp",3) != 0) retval = FALSE;
	else if (Host != url.host()) retval = FALSE;
	else if (!Login.isEmpty() && Login != url.user()) retval = FALSE;
	return retval;
}

const char* FtpProtocol::currentURL()
{
	QString		tmp("ftp://");
	if (!Login.isEmpty()) {tmp += Login; tmp += '@';}
	tmp += Host;
	if (Port >= 0) {
		tmp += ':';
		QString	num;
		num.setNum(Port);
		tmp += num;
	}
	tmp += CurrentPath;
	return tmp.data();
}
