/*
** 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 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- April 1998
 */

/*
 * FileTraversal.cpp : class to traverse a file hierarchy
 */

#include "stdafx.h"

#ifdef WIN32
#	include "resource.h"
#endif /* WIN32 */

#if TARGET_OS_MAC
#	include "GUSIInternal.h"
#	include "GUSIFileSpec.h"
#endif

#if TARGET_RT_MAC_CFM
#	include "GUSIDevice.h"
#endif /* TARGET_RT_MAC_CFM */

#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif /* HAVE_UNISTD_H */

#ifdef HAVE_ERRNO_H
#	include <errno.h>
#endif /* HAVE_ERRNO_H */

#include "FileTraversal.h"
#include "CPStr.h"
#include "umain.h"

#ifdef WIN32
#	include "AppConsole.h"
#	include "wincvsdebug.h"

#	ifdef _DEBUG
#	define new DEBUG_NEW
#	undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
#	endif
#endif /* WIN32 */

// in order to accelerate GUSI, we avoid using full paths
#if TARGET_RT_MAC_CFM
typedef GUSIDirectory * GUSIDirPtr;

static DIR * opendir(const GUSIFileSpec & spec)
{
	GUSIFileToken	file(spec, GUSIFileToken::kWillOpendir);
	
	GUSIDirectory * dir;
	if (GUSIDevice * device = file.Device())
		dir = device->opendir(file);
	else
	{
		GUSISetPosixError(ENOENT);
		dir = static_cast<GUSIDirectory *>(nil);
	}
	
	return dir ? reinterpret_cast<DIR *>(new GUSIDirPtr(dir)) : 0;
}

static int stat(const GUSIFileSpec & inspec, struct stat * sb)
{
	GUSIFileToken 	file(inspec, GUSIFileToken::kWillStat);
	
	if (GUSIDevice * device = file.Device())
		return device->stat(file, sb);

	GUSISetPosixError(ENOENT);
	return -1;
}
#endif

bool TraversalReport::gRunning = false;

kTraversal FileTraverse(const char *path, TraversalReport & report, const UFSSpec * macDirSpec)
{
	DIR *dirp = 0L;
	STRUCT_DIRENT *dp;
	struct stat sb;
	CStr fullname, errstr, dirname, tmpname;
	kTraversal res = kContinueTraversal;
	char *tmp;
	const UFSSpec *macspec = 0L;
#if defined(WIN32)
	unsigned long dbg_mask = GetWinCvsDebugMask(false);
	CStr tracestr;
#endif
	
	// used to restore the current directory
	static bool getPathFlag = false;
	char* szOriginalPath = NULL;
	USemaphore policeman(getPathFlag);
	USemaphore runstate(TraversalReport::gRunning);

	if(!policeman.IsEnteredTwice())
	{
		szOriginalPath = getcwd(NULL, MAX_PATH);
	}

#if !TARGET_OS_MAC
	if(path == 0L || (dirp = opendir(path)) == 0L)
#elif TARGET_RT_MAC_CFM
	GUSIFileSpec folderspec;
	UFSSpec foldermacspec;
	if(macDirSpec != 0L)
		folderspec = *macDirSpec;
	else if(path != 0L)
		folderspec = path;
	foldermacspec = folderspec;
	macspec = &foldermacspec;
	
	if((dirp = opendir(folderspec)) == 0L)
#else
	FSRef foldermacspec;
	if(macDirSpec != 0L)
		foldermacspec = *macDirSpec;
	else if(path != 0L)
		FSPathMakeRef(posix_to_utf8(path), &foldermacspec, NULL);
	macspec = &foldermacspec;
	if(path == 0L || (dirp = opendir(path)) == 0L)
#endif
	{
		errstr = "Error while accessing ";
		errstr << path;
		errstr << " (error " << errno << ')';
		res =  report.OnError(errstr, errno);
		goto exit;
	}

	tmpname = path;
	tmp = tmpname;
	char *next;
	if(tmpname.endsWith(kPathDelimiter))
		tmp[strlen(tmp) - 1] = '\0';
	next = tmp;
	while((tmp = strchr(next, kPathDelimiter)) != 0L)
	{
		next = ++tmp;
	}
	dirname = next;

	res = report.EnterDirectory(path, dirname, macspec);
	if(res != kContinueTraversal)
		goto abort;

	while ((dp = readdir (dirp)) != NULL)
	{
		res = report.OnIdle(path);
		if(res != kContinueTraversal)
			goto abort;

#if !TARGET_RT_MAC_CFM
		if(strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
			continue;
#endif /* !TARGET_RT_MAC_CFM */

		fullname = path;
		if(!fullname.endsWith(kPathDelimiter))
			fullname << kPathDelimiter;
		fullname << dp->d_name;

		macspec = 0L;
		int statres;
		
#if TARGET_RT_MAC_CFM
		GUSIFileSpec inspec(folderspec);
		inspec += dp->d_name;
		UFSSpec infspec = inspec;
		macspec = &infspec;

		statres = stat(inspec, &sb);
#elif TARGET_RT_MAC_MACHO
		GUSIFileSpec inspec(foldermacspec);
		inspec += dp->d_name;
		UFSSpec infspec = inspec;
		macspec = &infspec;

		statres = stat(fullname, &sb);
#else
#	if defined(WIN32)
		if ( (dbg_mask & wcvsdbg_trace_modified_checks) && (dbg_mask & wcvsdbg_verbose) )
		{
			AppendWinCvsDebugMask(wcvsdbg_trace_dst_checks
									| wcvsdbg_trace_stat);
			tracestr = "Stat(";
			tracestr << static_cast<const char *>(fullname);
			tracestr << ") ...\n";
			cvs_err(tracestr);
			OutputDebugString(tracestr);
		}
#	endif	// defined(WIN32)

		statres = stat(fullname, &sb);
#endif

	      
    if (statres != -1)
		{
			if(S_ISDIR(sb.st_mode))
			{
				res = report.OnDirectory(path, fullname, dp->d_name, sb, macspec);

        if(res == kSkipFile)
					continue;
				if(res != kContinueTraversal)
					goto abort;
   
        res = FileTraverse(fullname, report, macspec);
				if(res != kContinueTraversal)
					goto abort;

			}
			else if(S_ISREG(sb.st_mode))
			{				
				res = report.OnFile(path, fullname, dp->d_name, sb, macspec);
				if(res == kSkipFile)
					continue;
				if(res != kContinueTraversal)
					goto abort;
			}
#ifdef S_ISLNK
			else if(S_ISLNK(sb.st_mode))
			{
				res = report.OnAlias(path, fullname, dp->d_name, sb, macspec);
				if(res == kSkipFile)
					continue;
				if(res != kContinueTraversal)
					goto abort;
			}
#endif /* S_ISLNK */
			else
			{
				/* TODO : Unix devices */
				errstr = "Error while accessing ";
				errstr << fullname;
				errstr << " (error " << errno << ')';
				res = report.OnError(errstr, errno);
				goto abort;
			}
		}
		else
		{
			errstr = "Error while accessing ";
			errstr << fullname;
			errstr << " (error " << errno << ')';
			res = report.OnError(errstr, errno);
      
      // no abort, because all 
      // * files not read yet will be signed as missing and all 
      // * folder will not be displayed
      //goto abort;
		}
	}
	
	res = report.ExitDirectory(path);

abort:
	closedir (dirp);
exit:
	if(0L != szOriginalPath)
	{
#if !TARGET_RT_MAC_CFM
		chdir(szOriginalPath);
#endif
		free(szOriginalPath);
		szOriginalPath = NULL;
	}

#if defined(WIN32)
	SetWinCvsDebugMask(dbg_mask);
#endif	// defined(WIN32)

	return res;
}
