/***************************************************************************
                           cdir.cpp  -  description
                             -------------------
    begin                : Tue May 14 2002
    copyright            : (C) 2002-2005 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cdir.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WIN32
#include <io.h>
#else
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>

extern "C" {
#include "../gnulib/fsusage.h"
}
#endif

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>

/** */
CDir::CDir()
{
}

/** */
CDir::CDir( CString path )
{
	SetPath(path);
}

/** */
CDir::~CDir()
{
}

/** */
CString CDir::Extension( CString file )
{
	int i;
	CString s;

	if ( (i = file.FindRev('.')) != -1 )
		s = file.Mid(i+1,file.Length()-i-1);

	return s;
}

/** */
CString CDir::Path()
{
#ifdef WIN32
	return ConvertSeparators(sDrive+DIRSEPARATOR+sPath);
#else
	return sPath;
#endif
}

/** */
void CDir::SetPath( CString path )
{
	// convert separators to internal
	path.Swap('\\','/');

#ifdef WIN32
	// TODO: use _splitpath
	int i;

	// store drive letter
	if ( (i = path.Find(':',0)) == 1 )
	{
		sDrive = path.Left(i+1);
		path = path.Mid(i+1,path.Length()-i-1);

		// remove first separator
		if ( path.Find('/') == 0 )
		{
			path = path.Mid(1,path.Length()-1);
		}
	}
#endif

	// remove last '/'
	if ( ((1+path.FindRev('/')) == path.Length()) && (path.Length() > 1) )
	{
		sPath = path.Left( path.Length()-1 );
	}
	else
	{
		sPath = path;
	}
}

/** */
CString CDir::HomeDirPath()
{
	CString s;

#ifdef WIN32
	s = getenv("USERPROFILE");

	if ( s.IsEmpty() )
	{
		char buffer[_MAX_PATH];

		/* Get the current working directory: */
		if( _getcwd( buffer, _MAX_PATH ) != NULL )
		{
			s = buffer;
		}
	}
#else
	s = getenv("HOME");
#endif
	if ( s.IsEmpty() )
	{
		s = DIRSEPARATOR;
	}

	return s;
}

/** */
bool CDir::cd( CString path )
{
	sPath.Empty();

	if ( path == "." )
		return true;
	if ( path.IsEmpty() )
		return false;

#ifdef WIN32
	if ( _access( path.Data(), F_OK | R_OK ) == 0 )
#else
	if ( access( path.Data(), F_OK | R_OK ) == 0 )
#endif
	{
		SetPath(path);
		return true;
	}

	return false;
}

/** */
CString CDir::DirName()
{
	int pos = sPath.FindRev('/');

	if( pos == -1 )
		return sPath;

	return sPath.Mid( pos + 1, sPath.Length() - 1 - pos );
}

/** */
#ifdef WIN32
bool CDir::ReadEntrys( FilterSpec filterspec, CList<CFileInfo> * list, const bool /* checkPerms */ )
#else
bool CDir::ReadEntrys( FilterSpec filterspec, CList<CFileInfo> * list, const bool checkPerms )
#endif
{
	if ( !list )
		return false;
	
	if ( Path().IsEmpty() )
		return false;
	
	list->Clear();
	
#ifdef WIN32

	struct _finddatai64_t filedata;
	CString pathstar = Path();
	pathstar += DIRSEPARATOR;
	pathstar += '*';
	intptr_t fhandle = _findfirsti64( pathstar.Data(), &filedata );
	
	if ( fhandle == -1 )
	{
		return false;
	}
	else
	{
		do
		{
			if ( ((filterspec == Files) && ((filedata.attrib & _A_SUBDIR) == 0)) ||
			     ((filterspec == Dirs) && ((filedata.attrib & _A_SUBDIR) == _A_SUBDIR)) )
			{
				CFileInfo * fi = new CFileInfo();
				fi->name = filedata.name;
				fi->size = filedata.size;
				fi->m_bSymlink = false;
				fi->st_a_time = filedata.time_access;
				fi->st_m_time = filedata.time_write;
				fi->st_c_time = filedata.time_create;
				list->Add(fi);
			}
		}
		while ( _findnexti64( fhandle, &filedata ) == 0 );
		_findclose( fhandle );
	}

#else /* NOT WIN32 */

	DIR * dir;
	CFileInfo * FileInfo;
	dirent *file;
	CString s,d_name;
	struct stat buf;
	
	/*
	 * even though they are not needed for checkPerms == false
	 * gcc thinks that they "may be used uninitialized in this function"
	 * if the get calls are within if ( checkPerms ) { ... }
	 */
	const uid_t euid = geteuid();
	const gid_t egid = getegid();
	
	gid_t * groups = 0;
	int numgroups = 0;

	dir = opendir( Path().Data() );

	if ( dir == 0 )
		return false;

	if ( checkPerms )
	{
		numgroups = getgroups( 0, NULL );
		if ( numgroups > 0 )
		{
			groups = (gid_t*) malloc( numgroups * sizeof(gid_t) );
			if ( groups )
			{
				if ( getgroups( numgroups, groups ) != numgroups )
				{
					perror("CDir::ReadEntrys getgroups");
					free( groups );
					groups = 0;
					numgroups = 0;
				}
			}
		}
	}

	while ( (file = readdir(dir)) != 0 )
	{
		d_name.Set(file->d_name,strlen(file->d_name));

		if ( GetStat( d_name, &buf ) )
		{
			if ( (filterspec == Dirs) && ((buf.st_mode&S_IFMT)&S_IFDIR) )
			{
				FileInfo = new CFileInfo();
				FileInfo->name = d_name;
				FileInfo->size = 0;
				FileInfo->m_bSymlink = false;
				
				FileInfo->st_a_time = buf.st_atime;
				FileInfo->st_c_time = buf.st_ctime;
				FileInfo->st_m_time = buf.st_mtime;

				if ( checkPerms )
				{
					bool accessible = false;
				
					if ( (buf.st_mode & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH) )
					{
						accessible = true;
					}
					else if ( (buf.st_uid == euid) && ((buf.st_mode & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR)) )
					{
						accessible = true;
					}
					else if ( (buf.st_mode & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP) )
					{
						if ( buf.st_gid == egid )
						{
							accessible = true;
						}
						else
						{
							for ( int i = 0; i < numgroups; ++i )
							{
								if ( buf.st_gid == groups[i] )
								{
									accessible = true;
									break;
								}
							}
						}
					}
					
					if ( !accessible )
					{
						delete FileInfo;
						continue;
					}
				}

				if ( GetLStat( d_name, &buf) )
				{
					FileInfo->m_bSymlink = ((buf.st_mode&S_IFLNK)==S_IFLNK);
				}
				else
				{
					delete FileInfo;
					continue;
				}

				list->Add(FileInfo);
			}
			else if ( (filterspec == Files) && ((buf.st_mode&S_IFMT)&S_IFREG) )
			{
				FileInfo = new CFileInfo();
				FileInfo->name = d_name;
				FileInfo->size = buf.st_size;
				FileInfo->m_bSymlink = false;

				FileInfo->st_a_time = buf.st_atime;
				FileInfo->st_c_time = buf.st_ctime;
				FileInfo->st_m_time = buf.st_mtime;

				if ( checkPerms )
				{
					bool readable = false;
					
					if ( (buf.st_mode & S_IROTH) == S_IROTH )
					{
						readable = true;
					}
					else if ( (buf.st_uid == euid) && ((buf.st_mode & S_IRUSR) == S_IRUSR) )
					{
						readable = true;
					}
					else if ( (buf.st_mode & S_IRGRP) == S_IRGRP )
					{
						if ( buf.st_gid == egid )
						{
							readable = true;
						}
						else
						{
							for ( int i = 0; i < numgroups; ++i )
							{
								if ( buf.st_gid == groups[i] )
								{
									readable = true;
									break;
								}
							}
						}
					}
					
					if ( !readable )
					{
						delete FileInfo;
						continue;
					}
				}
				
				if ( GetLStat( d_name, &buf) )
				{
					FileInfo->m_bSymlink = ((buf.st_mode&S_IFLNK)==S_IFLNK);
				}
				else
				{
					delete FileInfo;
					continue;
				}

				list->Add(FileInfo);
			}
		}
	}

	closedir(dir);

	if ( groups )
	{
		free( groups );
	}

#endif /* WIN32 */

	return true;
}

/** */
bool CDir::IsDir( CString s, bool rel )
{
#ifdef WIN32
	struct _stati64 buf;
#else
	struct stat buf;
#endif

	if ( GetStat(s,&buf,rel) )
	{
		if ((buf.st_mode&S_IFMT)&S_IFDIR)
		{
			return true;
		}
	}

	return false;
}

/** */
bool CDir::IsFile( CString s, bool rel )
{
#ifdef WIN32
	struct _stati64 buf;
#else
	struct stat buf;
#endif

	if ( GetStat(s,&buf,rel) )
	{
		if ((buf.st_mode&S_IFMT)&S_IFREG)
		{
			return true;
		}
	}

	return false;
}

#ifdef WIN32

/** Some code repeated to avoid temp string when rel is false */
bool CDir::GetStat( CString & s, struct _stati64 * buf, bool rel )
{
	if ( !buf )
	{
		return false;
	}
	
	if ( rel )
	{
		CString p = Path();
		p += DIRSEPARATOR;
		p += s;
		
		/* it's never empty because DIRSEPARATOR is not nothing */
		return (_stati64( p.Data(), buf ) == 0);
	}
	else
	{
		if ( s.IsEmpty() )
		{
			return false;
		}
		else
		{
			return (_stati64( s.Data(), buf ) == 0);
		}
	}
}

/** Does not exist on WIN32 */
bool CDir::GetLStat( CString & /* s */, struct _stati64 * /* buf */, bool /* rel */ )
{
	return false;
}

#else // WIN32

/** Some code repeated to avoid temp string when rel is false */
bool CDir::GetStat( CString & s, struct stat * buf, bool rel )
{
	if ( !buf )
	{
		return false;
	}

	if ( rel )
	{
		CString p = sPath;
		p += DIRSEPARATOR;
		p += s;
		
		/* it's never empty because DIRSEPARATOR is not nothing */
		return (stat( p.Data(), buf ) == 0);
	}
	else
	{
		if ( s.IsEmpty() )
		{
			return false;
		}
		else
		{
			return (stat( s.Data(), buf ) == 0);
		}
	}
}

/** Some code repeated to avoid temp string when rel is false */
bool CDir::GetLStat( CString & s, struct stat * buf, bool rel )
{
	if ( !buf )
	{
		return false;
	}
	
	if ( rel )
	{
		CString p = sPath;
		p += DIRSEPARATOR;
		p += s;
		
		/* it's never empty because DIRSEPARATOR is not nothing */
		return (lstat( p.Data(), buf ) == 0);
	}
	else
	{
		if ( s.IsEmpty() )
		{
			return false;
		}
		else
		{
			return (lstat( s.Data(), buf ) == 0);
		}
	}
}

#endif // WIN32

/** */
ulonglong CDir::getFileSize( CString s, bool rel )
{
#ifdef WIN32
	struct _stati64 buf;
#else
	struct stat buf;
#endif

	if ( rel )
	{
#ifdef WIN32
		s = Path()+DIRSEPARATOR+s;
#else
		s = sPath + DIRSEPARATOR + s;
#endif
	}

	if ( s.IsEmpty() )
		return 0;

#ifdef WIN32
	if ( _stati64( s.Data(), &buf ) != 0 )
#else
	if ( stat( s.Data(), &buf ) != 0 )
#endif
	{
		//printf("CDir::getFileSize: stat: '%s' '%s'\n",strerror(errno),s.Data());
		return false;
	}

	return buf.st_size;
}

/** */
void CDir::SplitPathFile( CString s, CString & path, CString & file )
{
	int i;

	path.Empty();
	file.Empty();

	if ( (i = s.FindRev('\\')) == -1 )
		i = s.FindRev('/'); // for unix-kind folders (never seen ...)

	if ( i == -1 )
	{
		file = s;
	}
	else
	{
		path = s.Left(i+1);
		file = s.Mid(i+1,s.Length()-i-1);
	}
}

/** */
CString CDir::ConvertSeparators( CString path )
{
	return path.Replace('/',DIRSEPARATOR);
}

/** */
CString CDir::SimplePath( CString path )
{
	CString t;
	char c = 0;
	long i = 0;

	path.Swap('\\','/');

	// convert first ../
	if ( path.StartsWith( "../", 3 ) )
	{
		path.Data()[0] = '/';
		path.Data()[1] = '/';
	}

	// convert '..' -> '//'
	while( (i=path.Find( "/.." ,i )) != -1 )
	{
		if ( (path.Data()[i+3] == 0) || (path.Data()[i+3] == '/') )
		{
			path.Data()[i+1] = '/';
			path.Data()[i+2] = '/';
		}
		i++;
	}
	
	// remove double '/'
	
	char * buffer = (char*) malloc( path.Length() + 1 );
	
	if ( buffer == 0 )
	{
		perror("CDir::SimplePath malloc");
		return t;
	}
	
	long bufferpos = 0;
	
	for ( i = 0; i < path.Length(); ++i )
	{
		if ( (c == '/') && (path.Data()[i] == '/') )
			continue;

		c = path.Data()[i];
		buffer[bufferpos] = c;
		++bufferpos;
	}

	t.Set( buffer, bufferpos );
	free(buffer);

#ifdef WIN32
	return ConvertSeparators(t);
#else
	return t;
#endif
}

/** */
bool CDir::CreatePath( CString path )
{
	int i;
	CString s,s1;

	s  = SimplePath(path);

#ifdef WIN32
	if ( (i = s.Find(':',0)) != -1 )
	{
		sDrive = s.Left(i+1);
		s = s.Mid(i+1,s.Length()-i-1);
	}
#endif

	while ( s.NotEmpty() )
	{
		if ( (i = s.Find(DIRSEPARATOR)) != -1 )
		{
			if (s1.NotEmpty())
				s1 = s1 + DIRSEPARATOR + s.Left(i);
			else
				s1 = s.Left(i);
			s  = s.Mid(i+1,s.Length()-1-i);
		}
		else
		{
			if (s1.NotEmpty())
				s1 = s1 + DIRSEPARATOR + s;
			else
				s1 = s;
			s.Empty();
		}

		if ( s1.IsEmpty() )
		{
			continue;
		}

		if ( IsDir(s1) == false )
		{
#ifdef WIN32
			if ( _mkdir((Path()+DIRSEPARATOR+s1).Data()) != 0 )
#else
			if ( mkdir((sPath+DIRSEPARATOR+s1).Data(),0777) != 0 )
#endif
			{
				if ( errno != EEXIST )
				{
					printf("mkdir Error: '%s' '%s'\n",strerror(errno),(sPath+DIRSEPARATOR+s1).Data());
				}

				return false;
			}
		}
	}

	return true;
}

/** */
bool CDir::FreeDiscSpace( CString path, ulonglong * res )
{
	if ( !res )
	{
		return false;
	}

#ifdef WIN32
	ULARGE_INTEGER lpFreeBytesAvailableToCaller; // receives the number of bytes on
                                               // disk available to the caller
	ULARGE_INTEGER lpTotalNumberOfBytes;    // receives the number of bytes on disk
	ULARGE_INTEGER lpTotalNumberOfFreeBytes; // receives the free bytes on disk

	if ( GetDiskFreeSpaceEx( path.Data(), &lpFreeBytesAvailableToCaller,
				&lpTotalNumberOfBytes,
				&lpTotalNumberOfFreeBytes ) == true )
	{
		*res = lpTotalNumberOfFreeBytes.QuadPart;
		return true;
	}
	else
	{
		return false;
	}
#else
	struct fs_usage fsp;

	if ( get_fs_usage(path.Data(),path.Data(),&fsp) == 0 )
	{
		// printf("ok %d\n",fsp.fsu_bavail_top_bit_set);
		*res = fsp.fsu_bavail*fsp.fsu_blocksize;
		return true;
	}
	else
	{
		perror("CDir::FreeDiscSpace");
		return false;
	}

#endif
}

/** */
bool CDir::canReadFile( const CString & file, bool rel )
{
	CString p;
	
	if ( rel )
	{
		p = Path() + DIRSEPARATOR + file;
	}
	else
	{
		p = file;
	}

#ifdef WIN32
	return (_access( p.Data(), R_OK ) == 0);
#else	
	return  (access( p.Data(), R_OK ) == 0);
#endif
}

/** */
CString CDir::Canonicalize( const CString & path )
{
#if defined(WIN32)
	/* windows GUI does not have symlinks, NTFS does have junctions */
	return path;
#else
	if ( path.IsEmpty() || (path == ".") )
	{
		return Path();
	}
	
	// convert dir separators to internal (unix)
	CString s = path;
	
	s.Swap('\\','/');
	
	if ( s.Data()[0] != '/' )
	{
		s = sPath + "/" + s;
	}

#if defined(HAVE_CANONICALIZE_FILE_NAME)

	char * result = canonicalize_file_name( s.Data() );
	if ( result == 0 )
	{
		s.Empty();
	}
	else
	{
		s = result;
		free(result);
	}

#elif defined(HAVE_REALPATH)

	char * buffer = (char*) calloc(1,4100);
	if ( buffer == 0 )
	{
		printf("CDir::Canonicalize calloc failed\n");
		return CString();
	}
	else
	{
		char * result = realpath( s.Data(), buffer );
		if ( result == 0 )
		{
			s.Empty();
		}
		else
		{
			s = result;
		}
		
		free(buffer);
	}
#else
	return path;
#endif /* HAVE_CANONICALIZE_FILE_NAME or HAVE_REALPATH */

	return s;
#endif /* WIN32 */
}

/** */
bool CDir::RmDir( const CString & abspath )
{
	/*
	 * I think Windows "sort of" supports Unix directory
	 * separators.
	 */
	
	if ( abspath.IsEmpty() )
	{
		return false;
	}
	else
	{
#ifdef WIN32
		return _rmdir( abspath.Data() ) == 0;
#else
		return rmdir( abspath.Data() ) == 0;
#endif
	}
}
