#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>

#include <kapp.h>
#include <qregexp.h>
#include "fileprot.h"

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

bool FileProtocol::init()
{
	_dir = 0;
	in = out = 0;
	buffer = 0;
	return TRUE;
}

bool FileProtocol::cleanup()
{
	if (_dir) closedir(_dir);
	_dir = 0;
	if (in > 0) ::close(in);
	if (out > 0) ::close(out);
	in = out = 0;
	if (buffer) delete [] buffer;
	buffer = 0;
	return TRUE;
}

 void FileProtocol::thread_updateEntries(bool hidden, bool dirsOnly, bool showArchive)
{
// 3 states : 	- all files			dirsOnly = FALSE
//		- directory			dirsOnly = TRUE, filter = 0
//		- directory + filtered files	dirsOnly = TRUE, filter != 0
	if (!rescanFlag) return;
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,0);
	entries->clear();
	namedEntries->clear();
	_dir = opendir(CurrentPath.data());
	bool	ProcDir(strncmp(CurrentPath.data(),"/proc",5) == 0);
	if (_dir) {
		struct dirent	*file;
		FileInfo	*fi;
		SubProtocol	*subProt;
//		QRegExp		w(filter,TRUE,TRUE);
		QString		completeName;
		while ((file = readdir(_dir)) != 0) {
			if (strcmp(file->d_name,".") == 0 || strcmp(file->d_name,"..") == 0) continue;
			if (!hidden && *file->d_name == '.') continue;
//			if (dirsOnly && checkFilter && w.match(file->d_name) < 0) continue;
			completeName = (CurrentPath + file->d_name);
			fi = new FileInfo(completeName.data(),0);
			if (!fi->isDir() && dirsOnly && (!showArchive || (subProt=matchFileName(file->d_name,ProtList)) == 0)) {
				delete fi;
				continue;
			}
			QString		encodedName(file->d_name);
			KURL::encodeURL(encodedName);
			completeName = ("file:" + CurrentPath + encodedName);
			if (fi->isDir()) completeName += "/";
			else if (dirsOnly && showArchive) {
				completeName += "#";
				completeName += subProt->protocol;
				completeName += ":";
			}
			fi->setPath(completeName.data());
			KMimeExtension	*ext = 0;
			if (fi->isFile()) {
				ext = getMimeTypeFromExtension(fi);
				if (ext == 0) ext = getMimeTypeFromName(fi);
				if (ext == 0 && UseMagic && !ProcDir) 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);
			entries->append(fi);
			namedEntries->append(file->d_name);
			pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
			pthread_testcancel();
			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,0);
		}
		closedir(_dir);
		_dir = 0;
		rescanFlag = FALSE;
	}

/*    if (dir->isReadable()) {
        const QFileInfoList	*list = dir->entryInfoList(filter,sort);
	QFileInfoListIterator	it(*list);
	FileInfo		*item;
	for (int i=0 ; it.current(); ++it) {
		if (it.current()->fileName() == "." || it.current()->fileName() == "..") continue;
		item = new FileInfo(it.current());
		QString		completeName = "file:" + dir->absPath();
		if (completeName[completeName.length()-1] != '/') completeName += "/";
		completeName += item->fileName();
		if (item->isDir()) completeName += "/";
		item->setPath(completeName.data());
		item->setIndex(i);
		KMimeExtension	*ext = 0;
		if (!item->isDir()) {
			ext = getMimeTypeFromExtension(item);
			if (ext == 0) ext = getMimeTypeFromName(item);
			if (ext == 0 && UseMagic) ext = getMimeTypeFromMagic(item);
			if (ext != 0) item->setDescription(ext->Description.data());
			else item->setDescription(i18n("Unknown"));
		}
		else item->setDescription(i18n("Directory"));
		item->setMimeType(ext);
		i++;
		entries->append(item);
		namedEntries->append(item->fileName().data());
	}
	rescanFlag = FALSE;
    }*/
}

void FileProtocol::updateEntries(bool hidden, bool dirsOnly, bool showArchive)
{
	ProtocolCmd	*cmd = new ProtocolCmd(Prot_List,this,(void*)hidden,(void*)dirsOnly,(void*)showArchive);
	launchThread(cmd);
	th_ID = 0;
	delete cmd;
}

const FileInfoList* FileProtocol::entryInfoList(bool hidden, bool dirsOnly, bool showArchive)
{
    if (rescanFlag) updateEntries(hidden,dirsOnly,showArchive);
    return entries;
}

const QStrList* FileProtocol::entryList(bool hidden, bool dirsOnly, bool showArchive)
{
    if (rescanFlag) updateEntries(hidden,dirsOnly,showArchive);
    return namedEntries;
}

bool FileProtocol::thread_copyToProtocol(const char *filename, const char *targetDir, bool move, ProgressDlg *dlg)
{
	QString		remoteDir;
	QString		FileName(filename);
	if (targetDir) {
		if (targetDir[0] != '/') remoteDir = absPath() + targetDir;
		else remoteDir = targetDir;
	}
	else remoteDir = absPath();
	remoteDir += FileName.right(FileName.length()-FileName.findRev('/',FileName.length()-2)-1);
	if (FileName[FileName.length()-1] == '/') return copyDir(FileName.data(),remoteDir.data(),move,dlg);
	else return copyFile(FileName.data(),remoteDir.data(),move,dlg);
}

bool FileProtocol::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);
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	return retval;
}

bool FileProtocol::thread_copyFromProtocol(const char *filename, const char *targetDir, bool move, ProgressDlg *dlg)
{
	QString		localFile;
	if (*filename != '/') localFile = absPath() + filename;
	else localFile = filename;
	QString		remoteFile(targetDir);
	if (remoteFile[remoteFile.length()-1] != '/') remoteFile += "/";
	remoteFile += localFile.right(localFile.length()-localFile.findRev('/',localFile.length()-2)-1);
	if (localFile[localFile.length()-1] == '/') return copyDir(localFile.data(),remoteFile.data(),move,dlg);
	else return copyFile(localFile.data(),remoteFile.data(),move,dlg);
}

bool FileProtocol::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);
	launchThread(cmd);
	bool	retval = cmd->done && (bool)cmd->out;
	delete cmd;
	th_ID = 0;
	return retval;
}

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

bool FileProtocol::matchURL(KURL url)
{
	bool	retval = FALSE;
	if (!url.hasSubProtocol() && strncmp(url.protocol(),"file",4) == 0)
		if (url.url().length() >= Prefix.length() && url.url().find(Prefix.data()) != -1) retval = TRUE;
	return retval;
}

const char* FileProtocol::currentURL()
{
	QString	tmp("file:");
	tmp += absPath();
	if (tmp[tmp.length()-1] != '/') tmp += "/";
	return tmp.data();
}

void* FileProtocol::thread_protocolCmd(ProtocolCmd *cmd)
{
	switch (cmd->code) {
	   case Prot_List:
		thread_updateEntries((bool)cmd->parg1,(bool)cmd->parg2, (bool)cmd->parg3);
		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_CopyFrom:
		cmd->out = (void*)thread_copyFromProtocol((const char*)cmd->parg1,(const char*)cmd->parg2,(bool)cmd->parg3,(ProgressDlg*)cmd->parg4);
		break;
	   case Prot_RmDir:
		cmd->out = (void*)removeDir((const char*)cmd->parg1,(ProgressDlg*)cmd->parg2);
		break;
	}

	cmd->done = TRUE;
	return 0;
}

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

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

#include <errno.h>

bool FileProtocol::copyFile(const char *source, const char *target, bool move, ProgressDlg *dlg)
{
	if (move && rename(source,target)) return TRUE;
	int	size = 0, copied = 0, buflen = 0, written = 0;
	in = ::open(source,O_RDONLY);
	if (in < 0) {
		ErrorMsg = i18n("Unable to open file ");
		ErrorMsg += source;
		return FALSE;
	}
	out = ::open(target,O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (out < 0) {
		ErrorMsg = i18n("Unable to open file ");
		ErrorMsg += target;
		::close(in);
		return FALSE;
	}
	struct stat	in_st;
	buffer = new char[MAXBUFFER];
	if (::stat(source,&in_st) < 0 || buffer == 0) {
		ErrorMsg = i18n("Unable to allocate buffer");
		::close(in);
		::close(out);
		return FALSE;
	}
	size = in_st.st_size;
	if (dlg) {
		QString		msg = (move ? i18n("Moving ") : i18n("Copying "));
		msg += (strrchr(source,'/')+1);
		QString		msg2(i18n("From "));
		msg2 += source;
		dlg->setLabelText(msg.data(),msg2.data());
		dlg->setProgress(0);
	}
	while ((buflen = ::read(in,buffer,MAXBUFFER)) > 0) {
		if ((written = ::write(out,buffer,buflen)) != buflen) break;
		copied += buflen;
		if (dlg) dlg->setProgress((copied*100)/size);
	}
	delete [] buffer;
	buffer = 0;
	::close(in);
	::close(out);
	in = out = 0;
	bool	retval(TRUE);
	if (move && remove(source)) {
		ErrorMsg = i18n("Unable to remove file ");
		ErrorMsg += source;
		retval = FALSE;
	}
	if (buflen < 0 || (buflen > 0 && buflen != written)) {
		ErrorMsg = i18n("I/O error while transfering ");
		ErrorMsg += source;
		retval = FALSE;
	}
	else retval = TRUE;
	return retval;
}

bool FileProtocol::copyDir(const char *source, const char *target, bool move, ProgressDlg *dlg)
{
	if (move && rename(source,target)) return TRUE;
	_dir = opendir(source);
	if (_dir == 0) {
		ErrorMsg = i18n("Unable to open directory ");
		ErrorMsg += source;
		return FALSE;
	}
	struct dirent	*file;
	QString		targetDir(target);
	if (targetDir[targetDir.length()-1] != '/') targetDir += "/";
	QString		sourceDir(source);
	if (sourceDir[sourceDir.length()-1] != '/') sourceDir += "/";
	if (::access(targetDir,F_OK) != 0 && ::mkdir(targetDir.data(),S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) {
		closedir(_dir);
		_dir = 0;
		ErrorMsg = i18n("Unable to open target directory ");
		ErrorMsg += target;
		return FALSE;
	}
	QStrList	list;
	while ((file = readdir(_dir)) != 0) list.append(file->d_name);
	closedir(_dir);
	_dir = 0;
	QStrListIterator	it(list);
	for (;it.current();++it) {
		if (strcmp(it.current(),".") == 0 || strcmp(it.current(),"..") == 0) continue;
		QString		fileTarget(targetDir.data());
		QString		fileSource(sourceDir.data());
		fileSource += it.current();
		fileTarget += it.current();
		struct stat	file_st;
		lstat(fileSource.data(),&file_st);
		if (file_st.st_mode & S_IFDIR) {
			fileSource += "/";
			fileTarget += "/";
			if (!copyDir(fileSource.data(),fileTarget.data(),move,dlg)) break;
		}
		else if (file_st.st_mode & S_IFREG) {
			if (!copyFile(fileSource.data(),fileTarget.data(),move,dlg)) break;
		}
		else if (file_st.st_mode & S_IFLNK) {
		}
		else break;
	}
	bool	retval = (it.current() ? FALSE : TRUE);
	if (move && ::rmdir(source) != 0) {
		ErrorMsg += i18n("Unable to delete directory ");
		ErrorMsg += source;
		retval = FALSE;
	}
	return retval;
}

bool FileProtocol::removeDir(const char *name, ProgressDlg *dlg)
{
	_dir = opendir(name);
	if (_dir == 0) {
		ErrorMsg = i18n("Unable to open directory ");
		ErrorMsg += name;
		return FALSE;
	}
	struct dirent	*file;
	struct stat	file_st;
	QString		target(name);
	if (target[target.length()-1] != '/') target += "/";
	QStrList	list;
	while ((file = readdir(_dir)) != 0) list.append(file->d_name);
	closedir(_dir);
	_dir = 0;
	QStrListIterator	it(list);
	for (;it.current();++it) {
		if (strcmp(it.current(),".") == 0 || strcmp(it.current(),"..") == 0) continue;
		QString		targetFile(target.data());
		targetFile += it.current();
		lstat(targetFile.data(),&file_st);
		if ((file_st.st_mode & S_IFREG) || (file_st.st_mode & S_IFLNK)) {
			if (!removeFile(targetFile.data(),dlg)) break;
		}
		else if (file_st.st_mode & S_IFDIR) {
			targetFile += "/";
			if (!removeDir(targetFile.data(),dlg)) break;
		}
		else break;
	}
	bool	retval;
	if (it.current()) retval = FALSE;
	else retval = (::rmdir(target.data()) == 0 ? TRUE : FALSE);
	if (!retval) {
		ErrorMsg = i18n("Unable to delete directory ");
		ErrorMsg += name;
	}
	return retval;
}

bool FileProtocol::removeFile(const char *name, ProgressDlg *dlg)
{
	if (dlg) {
		QString		msg(i18n("Removing "));
		msg += (strrchr(name,'/')+1);
		QString		msg2(i18n("From "));
		msg2 += name;
		dlg->setProgress(100);
		dlg->setLabelText(msg.data(),msg2.data());
	}
	bool	retval = (::remove(name) == 0 ? TRUE : FALSE);
	pthread_testcancel();
	if (!retval) {
		ErrorMsg = i18n("Unable to delete file ");
		ErrorMsg += name;
	}
	return retval;
}

#if HAVE_FSTAB_H
#include <sys/vfs.h>
#elif HAVE_SYS_VFSTAB_H
#include <sys/statvfs.h>
#else
#include <sys/vfs.h>
#endif

uint FileProtocol::freeSize(const char *dirname)
{
#if HAVE_SYS_VFSTAB_H
	struct statvfs	buf;
	if (statvfs(dirname,&buf) == 0) return (uint)(((float)buf.f_bavail * buf.f_frsize));
#else
	struct statfs	buf;
	if (statfs(dirname,&buf) == 0) return (uint)(((float)buf.f_bavail * buf.f_bsize));
#endif;
	return 0;
}

bool FileProtocol::hasChilds(const char *dirname)
{
	struct stat	st;
	stat(dirname,&st);
	return (st.st_nlink > 2);
}

FileInfo* FileProtocol::getInfo(KURL& url)
{
	FileInfo	*fi = new FileInfo(url.path(),0);
	fi->setPath(url.url().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);
	return fi;
}
