/* $Header$ */
//Win32 xfileops.cpp
//contains implementation of tqappfileStd class, routines for manipulating a q4 application file
//Copyright (C) OMNIS Software Inc 1998
/*
Changes
Date			Edit				Bug					Description
06-Jun-25	jmg1531			ST/EC/1954	FileOps copy always replaced the destination on Linux.
03-Jun-25	jmg1529			ST/EC/1954	Issues with FileOps.$copy() on Linux.
28-Mar-23	PK1515			ST/EC/1776		FileOps does not update the list of mounted volumes on Windows
28-Dec-22	pk1482			ST/EC/1759		$copyfile creates empty folder if not copying from the same directory
06-Dec-22	CR0816			ST/FU/843		$filelist was excluding all directories with a leading dot in the name. Should only be current and parent.
03-Nov-22	rmm11791								XCode 14.1 warnings (all sprintf and snprintf calls replaced with OMsnprintf, changes not marked).
16-Jul-21	rmm11107		ST/FU/798		Added file filter classes.
02-Jul-21	caa2019			ST/FU/796		Added InfoCalcDirectorySize to $getfileinfo.
14-Apr-21	rmm10984								Removed compiler warnings on Windows platform.
15-Oct-20	caa0121			ST/FU/774		$createdir can now optionally create unexisting parent folders.
03-Aug-20	caa0090									Making caa0070 for Windows only.
18-Aug-20	caa0070			ST/EC/1605	copyfile didn't work with folders.
03-Mar-20	caa0002			ST/PF/1077	Deleting file/folder now exhibits consistent cross-platform behaviour.
14-Jan-20	rmm10372		ST/BE/1491	Added static method FileOps.$selectfilesinsystemviewer.
18-Dec-17	rmm9591									Tidied up Scanf and made a few performance improvements.
06-Oct-17	rmm9525			ST/FU/715		Problems using / as the path separator on Windows (regression caused by long pathname prefixes)
18-Jul-17	rmm9433			ST/FU/708		FileOps.$doesfileexist() returned true for an empty pathname on Windows.
14-Feb-17	rmmheadless							Support for headless Omnis server.
30-Aug-16	rmm9025			ST/NV/059		Added support for long pathnames to FileOps and to core qfile.
26-Aug-16	rmm9020									Renamed CHR...EncodedCharacters to CHR...Utf8 and modified their interface.Also made some other small improvements.
19-Aug-16	rmm9010			ST/EC/1416	Added support for large files (files with a size > maximum signed 32 bit integer).
19-Aug-16	rmm8939a								Tidied up some parameter issues.
21-Oct-14	rmm8448									Memory leaks.
19-Aug-13	rmm64bitux							Linux 64 bit port.
19-Mar-13	rmm64bit6								Changes required for compiling with 64 bit target.
23-Jul-08	rmm_mobile							Added hidden static functions: $extractcab and $makecab.
11-Sep-07	rmm6231			ST/BE/231		Updated fileops documentation for linked name - found a bug while doing it, and implemented Unix linked name support.
																	Also noticed and fixed multi-threading issue with AE6787.
13-Jun-07	AE6787			ST/EC/1104: doesfileexist on DIR failed when used on a shared folder
31-Aug-06	AE6723			ST/EC/1031: Added linkedname to fileops
11-Aug-06 MHn0483			ST/FU/486:  Added kFileOpsInfoIsDirectory.
02-Dec-05	AE6623			ST/FU/463: 	Changed default operation for buildvolumes to return drive letters
05-May-05	rmm5354			ST/DB/635		Problem with Boolean file info properties.
05-Jul-04 MHCW9										Mac CW9 changes
19-Feb-04	rmmuni_osx6							Mac OSX Unicode Port.
17-Dec-02 MHn0201									added $converthfstoposix and $convertposixtohfs for OSX.
25-Nov-02	AE6160			ST/NT/366: 	Strip trailing path seperators on Win platforms so $doesfileexist works
06-Feb-02	rmm4202									Better error code if pathname is bad.
26-Jun-01	AE5236			ST/WT/521 	Unix: Fileops entirefile functions were, incorrectly, assuming Intel format
20-Feb-00	rmm4063									Volume names were sometimes empty.
29 NOV 00 mpmCarbon								Implements $readentirefile and $writeentireile
24-Nov-00	AE5129			ST/FU/301: 	Random flags used during SetFileInfo
25-Feb-00	AE5018									Added Win2k support to components,fileops(old & new),and orfc
24-Jan-00	AE5000			ST/FU/245: 	Win32 fileops.$rename returned an error code even if ok
30-Nov-99	AE4991			ST/EC/441: 	Prevent WM_LBUTTONUP's when dbl-clicking on a file
20-Sep-99 MHUX001									Unix Changes.
23-Feb-99 MHn0040			ST/NV/023		Had to modify fileops.he as a result of win16 fix - Affects all platforms.
04-Feb-99 MHn0032			ST/NV/019		Added kFileOpsResSize and kFileOpsActualResSize for returning resource fork information.
10-Nov-98 AE4856 			ST/NV/007		Movefile now works even if destination name only contains a folder name
14-Oct-98	AE4845			ST/NV/012: 	Win32: changeCDir returning errors even though it succeeded
08-Oct-98	AE4841			ST/NV/011: 	Restructured cInfoFullName to include full path (on Windows) + faults on Mac
13-Jul-98	AE4781									Supply normal short name if fullname not present
10-Jun-98	rmm											AnsiToOem made a macro.
*/
#include <extcomp.he>
#include <extfval.he>
#include <hwnd.he>
#include <gdi.he>
#include "fileops.he"
#include <windowsx.h>
#if defined(iswin32) && !defined(isunix)
	// rmm_mobile:
	#include <fci.h>
	#include <fdi.h>
	#include <fcntl.h>
	#include <io.h>
	#include <stdio.h>
	#include <sys/stat.h>
#endif
#include <vector> // rmm10372

//#include <COMMDLG.H>
// MHUX001 begins.
#ifdef isunix
#include <windows.h>
#include <commdlg.h>
#include <unistd.h>	// rmm6231
#include <fstream> // jmg1529
#include <sys/stat.h> // jmg1529
#include <dirent.h> // jmg1529
#define FILE_SEPR_STR "/"
#define FILE_SEPR_CHR '/'
#else
#define FILE_SEPR_STR "\\"
#define FILE_SEPR_CHR '\\'
#endif
// MHUX001 ends.

// caa2670 start
#include <sstream>
#include <iomanip>
// caa2670 end

// AE5018 Starts
static BOOL Win2000dlgSupport()
{
#if defined(iswin32) && !defined(isunix)
	DWORD ver = WNDwinGetVersion(); // rmm10984
	if ( (ver & 0xff) >= 0x05 )	 // Win2000 ?
		return TRUE;
#endif
	return FALSE;
}

#define WIN32_OFNSIZE      76
#define WIN2000K_OFNSIZE   88

#ifndef OFN_ENABLESIZING
	#define OFN_ENABLESIZING 0x00800000
#endif
// AE5018 Ends

// Start rmm9025
#define MAX_PATH_FILEOPS 1024	// rmm9025
#ifndef isunix
	static qoschar *sLongPathnameUNCPrefix = L"\\\\?\\UNC\\";
	static qlong sLongPathnameUNCPrefixLen = (qlong)_tcslen(sLongPathnameUNCPrefix);

	static qoschar *sLongPathnamePrefix = L"\\\\?\\";
	static qlong sLongPathnamePrefixLen = (qlong)_tcslen(sLongPathnamePrefix);
#endif

// Store path in fldval, stripping Windows long pathname prefix if present
static void	sStorePathInFldval(qoschar *pPath, qlong pPathLen, EXTfldval &pFval)
{
#ifndef isunix
	if (pPathLen >= sLongPathnameUNCPrefixLen && !memcmp(pPath, sLongPathnameUNCPrefix, QOSBYTELEN(sLongPathnameUNCPrefixLen)))
	{
		pFval.setChar(L"\\\\", 2);
		pFval.concat(pPath + sLongPathnameUNCPrefixLen, pPathLen - sLongPathnameUNCPrefixLen);
	}
	else if (pPathLen >= sLongPathnamePrefixLen && !memcmp(pPath, sLongPathnamePrefix, QOSBYTELEN(sLongPathnamePrefixLen)))
		pFval.setChar(pPath + sLongPathnamePrefixLen, pPathLen - sLongPathnamePrefixLen);
	else
#endif
		pFval.setChar(pPath, pPathLen);
	}
// End rmm9025

static void addLine(qlong pWhat, EXTfldval& pPath, qlong pRetInfo, EXTqlist *pList, WIN32_FIND_DATA* pFindFile, strxxx *pFilter, FILEfilterMap *pFilterMap = 0); // rmm9025 // rmm11107

#ifndef isheadless	// rmmheadless
	// AE4991
	void gFOFlushMouseUpEvts()
	{
	#if defined(iswin32) && !defined(isunix)
		Sleep(100);
	#endif
		MSG msg;
		while (PeekMessage(&msg, NULL, WM_LBUTTONUP, WM_LBUTTONUP, PM_REMOVE)) {}
		while (PeekMessage(&msg, NULL, WM_RBUTTONUP, WM_RBUTTONUP, PM_REMOVE)) {}
		while (PeekMessage(&msg, NULL, WM_MBUTTONUP, WM_MBUTTONUP, PM_REMOVE)) {}
	}

	void CenterDialogWindow( HWND hdlg )
	{
		HWND hwndOwner;
		RECT rc, rcDlg, rcOwner;

		// Get the owner window and dialog box rectangles.

		hwndOwner = GetParent( hdlg );

		GetWindowRect( hwndOwner, &rcOwner );
		GetWindowRect( hdlg, &rcDlg );
		CopyRect( &rc, &rcOwner );

		// Offset the owner and dialog box rectangles so that
		// right and bottom values represent the width and
		// height, and then offset the owner again to discard
		// space taken up by the dialog box.

		OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
		OffsetRect(&rc, -rc.left, -rc.top);
		OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);

		// The new position is the sum of half the remaining
		// space and the owner's original position.

		SetWindowPos( hdlg,
    							HWND_TOP,
        					rcOwner.left + (rc.right / 2),
        					rcOwner.top + (rc.bottom / 2),
        					0, 0,          // ignores size arguments
        					SWP_NOSIZE);
	}

	UINT CALLBACK OFNHookProc( HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam )
	{
		switch ( uiMsg )
		{
			case WM_INITDIALOG:
			{
				CenterDialogWindow( hdlg );
				return( TRUE );
			}
			case WM_NOTIFY:
			{
				NMHDR sNmhdr;

				sNmhdr = ((LPOFNOTIFY)(lParam))->hdr;
				if ( sNmhdr.code == CDN_INITDONE )
				{
					CenterDialogWindow( GetParent( hdlg ) );
				}
				break;
			}
		}
		return( FALSE );
	}
#endif	// rmmheadless

void GetAssociatedApp(EXTfldval& pPathName, str255& pCreator) // rmm9025
{
	// rmm9025:
	qoschar creator[255]; creator[0] = 0;
	qlong dotPos = pPathName.lastPos('.');
	if (dotPos)
	{
		qHandle han = pPathName.getHandle(qfalse);
		qHandleTextPtr hp(han, 0);
		qchar *add = *hp;
		qchar *extension = &add[dotPos];

		CHRconvToOs cto(extension);
		if (GetProfileString( (qoschar *) QTEXT("Extensions"), cto.dataPtr(), (qoschar *) QTEXT(""), creator, 255))
		{
			if (_tcstok(creator, (qoschar *) QTEXT("^")) )
			{
				CharUpper(creator);
			}
		}
	}
	CHRconvFromOs::convFromOs(creator, (qlong) _tcslen(creator), pCreator);
}

OpsErr GetOmnisError( DWORD pWinError )
{
	switch( pWinError )
	{
		case NO_ERROR: return kFileOpsOK;
		case kFileOpsParamError: return kFileOpsParamError;
		case ERROR_FILE_NOT_FOUND:
		case ERROR_PATH_NOT_FOUND:
		case ERROR_NO_MORE_FILES:
		case ERROR_INVALID_DATA:
		case ERROR_BAD_ENVIRONMENT:
			return kFileOpsFileNotFound;

		case ERROR_TOO_MANY_OPEN_FILES:
			return kFileOpsTooManyFilesOpen;

		case ERROR_INVALID_ACCESS:
		case ERROR_ACCESS_DENIED:
			return kFileOpsPermissionDenied;

		case ERROR_INVALID_HANDLE:
			return kFileOpsBadFileRef;

		case ERROR_NOT_ENOUGH_MEMORY:
			return kFileOpsOutOfMemory;

		case ERROR_CURRENT_DIRECTORY:
		case ERROR_DUP_NAME:
		case ERROR_FILE_EXISTS:
			return kFileOpsAlreadyExists;

		case ERROR_LOCK_VIOLATION:
			return kFileOpsFileLocked;

		case ERROR_WRITE_PROTECT:
			return kFileOpsAlreadyWriteOpen;

		case ERROR_BAD_UNIT:
		case ERROR_INVALID_DRIVE:
		case ERROR_NOT_READY:
			return kFileOpsBadDrive;

		case ERROR_BAD_FORMAT:
		case ERROR_NOT_DOS_DISK:
			return kFileOpsInvalidFormat;

		case ERROR_INVALID_BLOCK:
			return kFileOpsDiskFull;

		case ERROR_NOT_SAME_DEVICE:
			return kFileOpsCrossDeviceLink;

		case ERROR_SEEK:
			return kFileOpsPositionBeforeStart;

		case ERROR_WRITE_FAULT:
		case ERROR_READ_FAULT:
			return kFileOpsDiskIOError;

		case ERROR_SHARING_VIOLATION:
			return kFileOpsMoreFilesOpen;

		case ERROR_HANDLE_EOF:
			return kFileOpsEndOfFile;

		case ERROR_HANDLE_DISK_FULL:
			return kFileOpsDiskFull;

		case ERROR_ALREADY_EXISTS:
			return kFileOpsAlreadyExists;
		case ERROR_BAD_PATHNAME:	// rmm4202
		case ERROR_INVALID_NAME:
			return kFileOpsBadName;
		// Start rmm9010
		case kFileOpsFileTooLarge:
			return kFileOpsFileTooLarge;
		// End rmm9010
		// caa0002 START
		case ERROR_DIR_NOT_EMPTY:
			return kFileOpsDirectoryNotEmpty;
		// caa0002 END
		default:                            
			return kFileOpsUnknownError;
	}
}

OpsErr GetError()
{
	return GetOmnisError( GetLastError() );
}

qshort getDriveConstant(qchar *pDrivePath )
{
	CHRconvToOs cto(pDrivePath);
	qshort volumeID = GetDriveType(cto.dataPtr());
	if ( volumeID!=0 )
	{
		if ( *pDrivePath==qchar('A') || *pDrivePath ==qchar('B') && volumeID==DRIVE_REMOVABLE )
		{
			volumeID = 1;
		}
	}
	return volumeID;
}

//PK1515 DWORD gLogicalDrives = 0;
void buildVolumes ( EXTqlist *pList, qlong pRetInfo )
{
	/* PK1515 - removed drive cache 
	static qbool sDrivesFound = qfalse;
	
	if ( !sDrivesFound )
	{
		gLogicalDrives = GetLogicalDrives();
		sDrivesFound = qtrue;
	}
	gLogicalDrives = GetLogicalDrives();
	*/
	DWORD dwDriveMask = GetLogicalDrives(); //  gLogicalDrives;

	qchar drivename[16]; drivename[0] = '?'; drivename[1] = ':'; drivename[2] = '\\'; drivename[3] = 0;
	for ( qshort drive = 0, wReturn = 0; drive < 26; drive++ ) 
	{
    if (dwDriveMask & 1)
    {   // drive exists. Insert or update.
			drivename[0] = drive + 'A';
			wReturn = getDriveConstant( &drivename[0] );
    	if ( wReturn!=0 )
    	{
				str255 volName( drivename[0] );
				volName.insert(':',2); volName.insert(FILE_SEPR_CHR,3); // MHUX001
				qlong line = pList->insertRow();
				qshort col=1;	EXTfldval cvalp;
				for ( qlong loop=1; loop <= cInfoDirectory ; loop<<=1 ) // MHn0032 // MHn0483 // rmmvs8: qlong rather than qint
				{
					if ( pRetInfo & loop )
					{
						pList->getColValRef( line , col, cvalp, qtrue );
						switch ( loop )
						{
							case cInfoName:			// AE6623 These now return the drive letter
							case cInfoName83:
							{
								qlong oldlen = volName[0]; volName[0] = 2;
								cvalp.setChar( volName );	
								volName[0] = (qchar) oldlen;
								break;
							}
							case cInfoFullName:
							{
								str255 fullName;
								DWORD serialNum, maxEntryLen, fileSysFlags;
								qoschar volNameBuf[256], fileSysName[256];
								CHRconvToOs ctoVolName(volName);
								if ( !GetVolumeInformation (ctoVolName.dataPtr(), 										// Name of drive
																 	volNameBuf, QOSCHARLEN(sizeof(volNameBuf)), 				// Receiving buffer for volume name
																 	&serialNum, 																				// Serial number
																 	&maxEntryLen, 																			// Maximum length of entry (ie filename etc..)
																 	&fileSysFlags, 																			// Flags of type FS_
																	fileSysName, QOSCHARLEN(sizeof(fileSysName)) 				// File system name & buffer length
																  ))
								{ // Failed ... just copy drive letter
									fullName = volName;
								}
								else
								{
									CHRconvFromOs::convFromOs(volNameBuf, (qlong) _tcslen(volNameBuf), fullName);
								}
								if (!fullName[0]) fullName = volName;	// rmm4063
								cvalp.setChar( fullName );
								break;
							}
							case cInfoReadOnly:			
							case cInfoCreatorCode:
							case cInfoTypeCode:
							case cInfoHidden:
							case cInfoSize:
							case cInfoActualSize:
							case cInfoCreated:
							case cInfoModified:
							case cInfoResSize:			 // MHn0032
							case cInfoActualResSize: // MHn0032
							{
								break;
							}
							// MHn0483 begins
							// Set directory to true for volumes.
							case cInfoDirectory: 
							{
								cvalp.setBool(qtrue);
								break;
							}
							// MHn0483 ends
						}
						col++;
					}
				}
    	}
		}
    dwDriveMask >>= 1;
	}
}

qlong tqfFileOpsObj::setFileInfo(EXTfldval& pPath, EXTCompInfo* pEci, qshort pNextParam ) // rmm9025
{
	qlong rtnVal = kFileOpsOK; 
	// Attempt to find file supplied
	WIN32_FIND_DATA findFile;	HANDLE hSearch;	
  findFile.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
  hSearch = FindFirstFile(ctoPath.dataPtr(), &findFile);

  if (hSearch != INVALID_HANDLE_VALUE)
  {
		EXTParamInfo *paramWhat  = ECOfindParamNum( pEci, pNextParam ); 
		EXTParamInfo *paramValue = ECOfindParamNum( pEci, pNextParam+1 ); 
		while ( paramWhat && paramValue )
		{
			EXTfldval what( (qfldval)paramWhat->mData );
			EXTfldval value( (qfldval)paramValue->mData );
			qlong whatType = what.getLong();
			DWORD fileAttrs = 0; // AE5129 Much better to initialise this!
			switch ( whatType )
			{
				case cInfoReadOnly:			
				{
					if (value.getLong()==1) fileAttrs |= FILE_ATTRIBUTE_READONLY;					
					else fileAttrs &= (~FILE_ATTRIBUTE_READONLY);			
					if (!SetFileAttributes(ctoPath.dataPtr(), fileAttrs)) rtnVal = (qlong)GetError();
					break;
				}
				case cInfoHidden:
				{
					if (value.getLong()==1) fileAttrs |= FILE_ATTRIBUTE_HIDDEN;
					else fileAttrs &= (~FILE_ATTRIBUTE_HIDDEN);
					if (!SetFileAttributes(ctoPath.dataPtr(), fileAttrs)) rtnVal = (qlong)GetError();
					break;
				}
				case cInfoCreatorCode:
				case cInfoTypeCode:
				case cInfoCreated:
				case cInfoModified:
				case cInfoResSize:			 // MHn0032
				case cInfoActualResSize: // MHn0032
				case cInfoDirectory:		 // MHn0483
					break;
			}
			paramWhat   = ECOfindParamNum( pEci, pNextParam ); 
			paramValue  = ECOfindParamNum( pEci, pNextParam+1 ); 
			pNextParam +=2;
		}			
    FindClose( hSearch );
	}
	return rtnVal;
}


OpsErr tqfFileOpsObj::getFileInfo(EXTfldval& pPath, qlong pRetInfo, EXTqlist*& pList ) // rmm9025
{
	pList = new EXTqlist(listVlen);
	defineList ( pList, pRetInfo );

	// Attempt to find file supplied
	WIN32_FIND_DATA findFile;	HANDLE hSearch;	
  findFile.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
  hSearch = FindFirstFile(ctoPath.dataPtr(), &findFile);
  if (hSearch != INVALID_HANDLE_VALUE)
  {
		addLine((cIncludeDirectories|cIncludeFiles), pPath, pRetInfo, pList, &findFile, NULL);
    FindClose( hSearch );
	}
	return kFileOpsOK;
}


OpsErr	tqfFileOpsObj::createDir(EXTfldval& pPath, qbool createParentFolders) // rmm9025 // caa0121
{
	OpsErr rtnVal = kFileOpsParamError;
	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
	// caa0121 start // caa0121a modified logic a little so we can get back the correct error code
	if (createParentFolders)
	{
		std::string::size_type pos = 0;
		std::string path; pPath.getchar(path);
		if (!path.empty() && path.back() != EXTfile::pathSeparator())
			path += EXTfile::pathSeparator();
		while ((pos = path.find_first_of(EXTfile::pathSeparator(), pos)) != std::string::npos)
		{
			EXTfldval parentPath;
			parentPath.setchar(path.substr(0, pos));
			rtnVal = createDir(parentPath, qfalse);
			++pos;
		}
	// caa0121 end
	}
	else
	{
		if (!CreateDirectory(ctoPath.dataPtr(), NULL))
			rtnVal = GetError();
		else
			rtnVal = kFileOpsOK; // caa0121
	}
	return rtnVal;
}

OpsErr	tqfFileOpsObj::deleteFile(EXTfldval& pPath, qbool deleteContents, qbool recursive) // MHCW9 // rmm9025 // caa0002
{
	OpsErr rtnVal = kFileOpsParamError;
	WIN32_FIND_DATA findFile;	HANDLE hSearch;
  findFile.dwFileAttributes = FILE_ATTRIBUTE_NORMAL	|	FILE_ATTRIBUTE_DIRECTORY;
	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
  hSearch = FindFirstFile(ctoPath.dataPtr(), &findFile);
	if( hSearch != INVALID_HANDLE_VALUE )
	{
		// caa0002 START
		if (findFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			EXTfldval path; path.setChar(ctoPath.dataPtr(), ctoPath.len());
			if (deleteContents && recursive)
			{
				if (!RemoveDirectory(ctoPath.dataPtr())) // Directory may be empty
				{
					rtnVal = tqfFileOpsObj::deleteDirectoryContents(path, qtrue);
					// Now that we removed its children, we can delete the directory
					(!RemoveDirectory(ctoPath.dataPtr())) ? rtnVal = GetError() : rtnVal = kFileOpsOK;
				}
				else
					rtnVal = kFileOpsOK;
			}
			else if (deleteContents)
				rtnVal = tqfFileOpsObj::deleteDirectoryContents(path, qfalse);
			else if (!deleteContents && !recursive)
				(!RemoveDirectory(ctoPath.dataPtr())) ? rtnVal = GetError() : rtnVal = kFileOpsOK;
			else
				rtnVal = kFileOpsDirectoryNotEmpty;
		}
		else
			(!DeleteFile(ctoPath.dataPtr())) ? rtnVal = GetError() : rtnVal = kFileOpsOK;

		FindClose(hSearch);
	}
	else rtnVal = GetError();

	// caa0002 END
	return rtnVal;
}

// caa0002 START
OpsErr tqfFileOpsObj::deleteDirectoryContents(EXTfldval& pPath, qbool recursive)
{
	str255 path(pPath.getChar());

#ifdef isunix
	str255 allFilesMask(QTEXT("/*"));
	str255 pathSep(QTEXT("/"));
#else
	str255 allFilesMask(QTEXT("\\*"));
	str255 pathSep(QTEXT("\\"));
#endif

	OpsErr rtnVal = kFileOpsOK;
	WIN32_FIND_DATA ffd;

	str255 searchPath(path);
	searchPath.concat(allFilesMask);

	CHRconvToOs cto(searchPath);

	HANDLE hSearch = FindFirstFile(cto.dataPtr(), &ffd);
	if (hSearch != INVALID_HANDLE_VALUE)
	{
		do
		{
			if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				if (recursive)
				{
					if (strcmp((const char*)ffd.cFileName, ".") != 0 && strcmp((const char*)ffd.cFileName, "..") != 0)
					{
						str255 fileName(ffd.cFileName);
						str255 dirPath(path); dirPath.concat(pathSep, fileName);

						CHRconvToOs dirCto(dirPath);

						if (!RemoveDirectory(dirCto.dataPtr())) // Directory could be empty
						{
							EXTfldval directory;
							directory.setChar(dirPath);
							rtnVal = tqfFileOpsObj::deleteDirectoryContents(directory, qtrue);

							// Delete all its children, we can try delete directory again
							(!RemoveDirectory(dirCto.dataPtr())) ? rtnVal = GetError() : rtnVal = kFileOpsOK;
						}
					}
				}
			}
			else
			{
				str255 fileName(ffd.cFileName);
				str255 filePath(path); filePath.concat(pathSep, fileName);

				CHRconvToOs fileCto(filePath);

				(!DeleteFile(fileCto.dataPtr())) ? rtnVal = GetError() : rtnVal = kFileOpsOK;
			}
		} while (FindNextFile(hSearch, &ffd));
		FindClose(hSearch);
	}
	return rtnVal;
}
// caa0002 END

// Start jmg1529: Linux-specific file-system helper functions
#if defined(isunix)

// Utility: check if a path is a directory
bool is_directory(const std::string& path) {
    struct stat st;
    return (stat(path.c_str(), &st) == 0) && S_ISDIR(st.st_mode);
}

// Utility: get basename (e.g., "dir/file.txt" -> "file.txt")
std::string get_basename(const std::string& path) {
    char* path_dup = strdup(path.c_str()); // basename may modify input
    std::string base = basename(path_dup);
    free(path_dup);
    return base;
}

bool unix_copy_file(const std::string& src_file, const std::string& dst_file) {
    std::string final_dst = dst_file;

    if (is_directory(dst_file)) {
        // Append basename of src_file to the directory path
        if (dst_file.back() != '/')
            final_dst += '/';
        final_dst += get_basename(src_file);
    }

    std::ifstream src(src_file, std::ios::binary);
    std::ofstream dst(final_dst, std::ios::binary);
    if (!src || !dst) {
			SetLastError(kFileOpsNoPath); // jmg1531
      return false;
    }

    dst << src.rdbuf();
		dst.close();
		src.close();

		// Copy permissions to new file
		struct stat statv;
		if (stat(src_file.c_str(), &statv) == 0) {
			chmod(final_dst.c_str(), statv.st_mode & 07777); // apply only permission bits
		}


    return true;
}

bool unix_create_directory_if_not_exists(const std::string& path, mode_t mode = 0755) {
	struct stat st;
	if (stat(path.c_str(), &st) != 0) {
		if (mkdir(path.c_str(), mode) != 0)
			return false;
	}
	else if (!S_ISDIR(st.st_mode))
		return false;

	return true;
};

bool unix_copy_directory_recursive(const std::string& src, const std::string& dst) {
	DIR* dir = opendir(src.c_str());
	if (!dir) {
		return false;
	}

	if (!unix_create_directory_if_not_exists(dst)) {
		closedir(dir);
		return false;
	}

	struct dirent* entry;
	while ((entry = readdir(dir)) != nullptr) {
		std::string name = entry->d_name;
		if (name == "." || name == "..")
			continue;

		std::string src_path = src + "/" + name;
		std::string dst_path = dst + "/" + name;

		struct stat st;
		if (stat(src_path.c_str(), &st) != 0)
			continue;

		if (S_ISDIR(st.st_mode))
		{
			if (!unix_copy_directory_recursive(src_path, dst_path)) {
				closedir(dir);
				return false;
			}
		}
		else if (S_ISREG(st.st_mode))
		{
			if (!unix_copy_file(src_path, dst_path)) {
				closedir(dir);
				return false;
			}
		}
		// Skipping symlinks and special files for safety...
	}

	closedir(dir);
	return true;
};

#endif
// End jmg1529

OpsErr	tqfFileOpsObj::copyFile(EXTfldval &pFromPath, EXTfldval &pToPath) // rmm9025
{
	OpsErr rtnVal = kFileOpsParamError;
	CHRconvToOs ctoFromPath(pFromPath, qtrue);	// rmm9025
	CHRconvToOs ctoToPath(pToPath, qtrue);			// rmm9025

	if (!doesFileExist(pFromPath)) // jmg1529
		return kFileOpsFileNotFound; // jmg1529
	// caa0070 start
#if defined(isunix) //caa0090
		// Start jmg1529
		// caa start: stop relying on homnisxi
		// plus, we must do something like this because
		// we need to support rhel8 for the time being

		if (tqfFileOpsObj::isDirectory(pFromPath)) {
			if (unix_copy_directory_recursive(ctoFromPath.dataPtr(), ctoToPath.dataPtr())) {
				rtnVal = kFileOpsOK;
			} else {
				rtnVal = GetError();
			}
		}
		else {
			if (unix_copy_file(ctoFromPath.dataPtr(), ctoToPath.dataPtr())) {
				rtnVal = kFileOpsOK;
			} else {
				rtnVal = GetError();
			}
		}
		// End jmg1529
#else
	if (tqfFileOpsObj::isDirectory(pFromPath)) // jmg1529
	{
			SHFILEOPSTRUCT sfo;
			fillc(&sfo, sizeof(sfo), 0);
			sfo.wFunc = FO_COPY;

			//pk1482 SHFileOperation - required double null termination to work.
			qoschar* fromBuffer = ctoFromPath.dataPtr(); qlong fromBuffLen = ctoFromPath.len();
			if (ctoFromPath.internalBufferSize() > fromBuffLen+1) fromBuffer[fromBuffLen +1] = 0;

			qoschar* toBuffer = ctoToPath.dataPtr(); qlong toBuffLen = ctoToPath.len();
			if (ctoToPath.internalBufferSize() > toBuffLen+1) toBuffer[toBuffLen + 1] = 0;

			sfo.pFrom = ctoFromPath.dataPtr();
			sfo.pTo = ctoToPath.dataPtr();
			sfo.fFlags = FOF_SILENT | FOF_NOCOPYSECURITYATTRIBS | FOF_NOCONFIRMATION;
			//pk1482 SHFileOperation returns 0 for success and doesn't work with GetError()
			//if (!SHFileOperation(&sfo)) rtnVal = GetError();
		if (SHFileOperation(&sfo) != 0)  rtnVal = kFileOpsBadName;
			else
				rtnVal = kFileOpsOK;
	}
	else
	{
		if (!CopyFile(ctoFromPath.dataPtr(), ctoToPath.dataPtr(), !tqfFileOpsObj::isDirectory(pToPath))) // jmg1529
			rtnVal = GetError();
		else
			rtnVal = kFileOpsOK;
	}
#endif // caa0090
	// caa0070 end
	return rtnVal;
}


OpsErr	tqfFileOpsObj::moveFile(EXTfldval& pFromPath, EXTfldval& pToPath) // rmm9025
{
	OpsErr rtnVal = kFileOpsParamError;
	// AE4856 Starts
	if (isDirectory(pToPath))
	{	// Directory only specified, therefore concat name on end
		// Start rmm9025
		qlong pos = pFromPath.lastPos(FILE_SEPR_CHR);
		if (pos)
		{
			qHandle han = pFromPath.getHandle(qfalse);
			qHandleTextPtr hp(han, 0);
			qchar *fromPath = *hp;
			pToPath.concat(&fromPath[pos - 1], hp.charLen() - pos + 1);
		}
		// End rmm9025
	}
	// AE4856 Ends
	CHRconvToOs ctoFromPath(pFromPath, qtrue);	// rmm9025
	CHRconvToOs ctoToPath(pToPath, qtrue);			// rmm9025

	if (!MoveFile(ctoFromPath.dataPtr(), ctoToPath.dataPtr())) 
	{
		rtnVal = GetError();
	}
	else
	{
		rtnVal = kFileOpsOK;
	}
	return rtnVal;
}

// caa2630 start
qbool tqfFileOpsObj::doesFileExist(EXTfldval& pPath)
{
	qbool rtnVal;
	OpsErr irrelevant = doesFileExist(pPath, rtnVal);
	return rtnVal;
}
// caa2630 end

// caa2670 start
#if defined(iswin32) && !defined(isunix)

	std::string tqfFileOpsObj::getFileID(const std::string& pPath)
	{
		std::wstring wPath(pPath.begin(), pPath.end());

		HANDLE h = CreateFile(
			wPath.c_str(),
			0,
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
			NULL,
			OPEN_EXISTING,
			FILE_FLAG_BACKUP_SEMANTICS,
			NULL
		);

		if (INVALID_HANDLE_VALUE == h)
			return "";

		FILE_ID_INFO finfo;
		if (!GetFileInformationByHandleEx(h, FileIdInfo, &finfo, sizeof(finfo))) {
			CloseHandle(h);
			return "";
		}
		CloseHandle(h);

		std::ostringstream oss;
		for (int i = 15; i >= 0; i--) {
			oss << std::hex << std::setw(2) << std::setfill('0')
				<< (int)finfo.FileId.Identifier[i];
		}
		std::string fileId = oss.str();

		return fileId;
	}

#else

	std::string tqfFileOpsObj::getFileID(const std::string& pPath)
	{
		struct stat st;
		if (stat(pPath.c_str(), &st) != 0)
			return "";

		std::ostringstream oss;
		oss << st.st_ino;
		return oss.str();
	}

#endif // iswin32 && !isunix
// caa2670 end

OpsErr	tqfFileOpsObj::doesFileExist(EXTfldval& pPath, qbool& pExists,qbool pNested) // MHCW9 // rmm6231 // rmm9025
{
	// Start rmm9433
	if (!pPath.getCharLen())
	{
		pExists = qfalse;
		return kFileOpsOK;
	}
	// End rmm9433
	OpsErr rtnVal = kFileOpsParamError;
	WIN32_FIND_DATA fi;
	HANDLE hSearch;

	fi.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;

	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
	// Start rmm9025: Strip trailing separators
	qoschar *add = ctoPath.dataPtr() + ctoPath.len() - 1;
	while (add >= ctoPath.dataPtr() && (FILE_SEPR_CHR == *add || '/' == *add)) // rmm9525
		*add-- = 0;	// rmm9525
	// End rmm9025
	hSearch = FindFirstFile(ctoPath.dataPtr(), &fi);
	if ( hSearch != INVALID_HANDLE_VALUE )
	{
		FindClose( hSearch );
		pExists = qtrue;
		rtnVal = kFileOpsOK;
	}
	else
	{
		pExists = qfalse;
		rtnVal = GetLastError();
		if ( rtnVal == ERROR_NO_MORE_FILES )
			rtnVal = kFileOpsOK;
		else
		{
			rtnVal = GetOmnisError( rtnVal );
			// AE6787 Starts Maybe a PC sharing directory request...have another go
			if (!pNested) // rmm6231
			{
				// rmm_mobile: use a copy of path, as it is modified here
				// Start rmm9025
				EXTfldval path;
				path = pPath;
				// End rmm9025
				path.concat(str15(QTEXT(FILE_SEPR_STR)));	
				path.concat(str15(QTEXT("*.*")));
				OpsErr rtnVal2 = doesFileExist(path,pExists,qtrue); // rmm6231
				if ( rtnVal2==kFileOpsOK )
					return rtnVal2;
				else
					pExists = qfalse;
			}
			// AE6787 Ends
		}
	}
	return rtnVal;
}

OpsErr	tqfFileOpsObj::changeCDir(EXTfldval& pPath) // rmm9025
{
	OpsErr rtnVal = kFileOpsParamError;
	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
	if (SetCurrentDirectory(ctoPath.dataPtr()))
		rtnVal = kFileOpsOK; // AE4845
	else
		rtnVal = GetError();
	return rtnVal;
}

OpsErr	tqfFileOpsObj::setCreator(EXTfldval& pPath,str255* pFileType,str255* pCreator) // rmm9025
{
	return kFileOpsNoOperation;
}

OpsErr	tqfFileOpsObj::splitPath(EXTfldval& pPath,strxxx& pDr, EXTfldval& pDir,strxxx& pFile,strxxx& pExt) // rmm9025
{
	OpsErr rtnVal = kFileOpsOK;
	qoschar drBuf[256], dirBuf[256], fileBuf[256], extBuf[256];
	CHRconvToOs ctoPath(pPath); // rmm9025: Note - cannot use pathname prefixes with _tsplitpath
	_tsplitpath( ctoPath.dataPtr(), drBuf, dirBuf, fileBuf, extBuf);
	// Return results
	CHRconvFromOs::convFromOs(drBuf, (qlong) _tcslen(drBuf), pDr);
	// Start rmm9025
	CHRconvFromOs cfoDir(dirBuf, (qlong)_tcslen(dirBuf));
	pDir.setChar(cfoDir.dataPtr(), cfoDir.len());
	// End rmm9025
	CHRconvFromOs::convFromOs(fileBuf, (qlong) _tcslen(fileBuf), pFile);
	CHRconvFromOs::convFromOs(extBuf, (qlong) _tcslen(extBuf), pExt);
	return rtnVal;
}

OpsErr	tqfFileOpsObj::getFilesList(qbool pFolders,EXTqlist* pList,qshort pFCol,EXTfldval& pPath,str255* pFType,qbool p83) // rmm9025
{
	OpsErr rtnVal = kFileOpsParamError;
	WIN32_FIND_DATA findFile; HANDLE hFindFile = INVALID_HANDLE_VALUE; str255 fname; EXTfldval fvallst;
  // Add ALL names to the list.
	CHRconvToOs ctoPath(pPath, qtrue); // rmm9025
  hFindFile = FindFirstFile(ctoPath.dataPtr(), &findFile);
  if(hFindFile != INVALID_HANDLE_VALUE)
	{
  	do
	  {
	  	if ( ( !pFolders												// If files only
			     && !(findFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)	)   // check NOT sub-directory
           || ( pFolders  										  // If folders only
		            &&  findFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY     //    check if sub directory
	              &&  findFile.cFileName[0] != '.' ))	                         //    avoid adding '.' and '..' subdirectories
      {
				fname = ( p83 && findFile.cAlternateFileName[0]) ? str255( QOSTEXT(findFile.cAlternateFileName) ) : str255(QOSTEXT(findFile.cFileName));
				qlong row = pList->insertRow();	
				pList->getColValRef(row,pFCol,fvallst,qtrue); fvallst.setChar(fname);
	    }
	  }
		while( FindNextFile( hFindFile, &findFile) );
		rtnVal = GetLastError();			  // If error get error number
		FindClose(hFindFile);
	}
	else
		rtnVal = GetLastError();			  // If error get error number
	if ( rtnVal==ERROR_NO_MORE_FILES )
		rtnVal = NO_ERROR;
	return GetOmnisError( rtnVal );
}

void	tqfFileOpsObj::getCDir(EXTfldval& pPath) // rmm9025
{
	// rmm9025:
	qoschar dirBuf[MAX_PATH_FILEOPS];
	DWORD len1 = GetCurrentDirectory(MAX_PATH_FILEOPS, dirBuf);
	sStorePathInFldval(dirBuf, len1, pPath);
}

OpsErr tqfFileOpsObj::rename(EXTfldval& pOldName, EXTfldval& pNewName) // rmm9025
{
	CHRconvToOs ctoOld(pOldName, qtrue);	// rmm9025
	CHRconvToOs ctoNew(pNewName, qtrue);	// rmm9025
	if (MoveFile(ctoOld.dataPtr(), ctoNew.dataPtr())) // AE5000
		return kFileOpsOK;
	return GetError();
}

OpsErr tqfFileOpsObj::getFileList(qlong pWhat, EXTfldval &pPath, qlong pRetInfo, strxxx &pFilter, EXTqlist*& pList)  // rmm9025
{
	pList = new EXTqlist(listVlen);
	// Volume name always returns in name
	if ( pWhat & cIncludeVolumes ) pRetInfo |= cInfoName;
	defineList ( pList, pRetInfo );
	
	if ( pWhat & cIncludeVolumes )
	{
		buildVolumes ( pList, pRetInfo );
	}

	// Start rmm9025
	EXTfldval path;
	path = pPath;
	qlong pathLen = path.getCharLen();
	if (pathLen && path.lastPos(FILE_SEPR_CHR) != pathLen)
		path.concat(FILE_SEPR_CHR);
	path.concat(str15(QTEXT("*.*")));
	// End rmm9025
	
	WIN32_FIND_DATA findFile;
  HANDLE hFindFile = INVALID_HANDLE_VALUE;    //Find Handle
	findFile.dwFileAttributes = FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_READONLY |FILE_ATTRIBUTE_DIRECTORY;
  // Add ALL names to the list.
	CHRconvToOs ctoPath(path, qtrue); // rmm9025
	FILEfilterMap filterMap;	// rmm11107
  hFindFile = FindFirstFile(ctoPath.dataPtr(), &findFile);
  if (hFindFile != INVALID_HANDLE_VALUE)
	{
		do
		{
			qbool isDir = (findFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!=0;
			qbool isCurrentOrParentDir = qfalse;	// CR0816
			if ( ( pWhat & cIncludeDirectories && isDir) || (pWhat & cIncludeFiles && !isDir) )
			{
				// Start CR0816
				if (isDir && findFile.cFileName[0] == '.')
					isCurrentOrParentDir = (1 == _tcslen(findFile.cFileName)) || (2 == _tcslen(findFile.cFileName) && findFile.cFileName[1] == '.');
				if ( (isDir && !isCurrentOrParentDir) || !isDir )	// End CR0816
					addLine(pWhat, path, pRetInfo, pList, &findFile, &pFilter, &filterMap); // rmm9025 // rmm11107
			}
	  }
		while( FindNextFile( hFindFile, &findFile) );
		FindClose(hFindFile);
	}
	return kFileOpsOK;
}

#ifndef isunix
	#include <shlguid.h>
	#include <shlobj.h>
	#include <objbase.h>
#endif
static HRESULT ResolveIt(HWND hwnd, EXTfldval &pLinkFilePath, EXTfldval &pResolvedPath, qbool &pIsDir, qbool &pIsDrive ) // rmm9025
{ 
	pResolvedPath.setEmpty(fftCharacter, dpDefault);	// rmm9025
#ifdef isunix // MHUX001
	// rmm6231: get the symbolic link target
	char linkBuf[MAX_PATH];
	CHRconvToUtf8 ctu(pLinkFilePath); // +1 for null terminator // rmm9025
	int charCount = readlink((const char *) ctu.dataPtr(), linkBuf, sizeof(linkBuf)); // rmm64bitux
	if (charCount > 0)
	{
		CHRconvFromUtf8 cfu((qbyte *) linkBuf, charCount);
		pResolvedPath.setChar(cfu.dataPtr(), cfu.len());	// rmm9025
		return (HRESULT) 1; // rmm64bitux
	}
  return 0; // MHUX001
#else // MHUX001
	HRESULT hres; 
	IShellLink* psl; 
	qoschar szGotPath[MAX_PATH_FILEOPS]; // rmm9025
	WIN32_FIND_DATA wfd; 

	// Get a pointer to the IShellLink interface. 
	hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); 
	if (SUCCEEDED(hres)) 
	{ 
		IPersistFile* ppf; 

		// Get a pointer to the IPersistFile interface. 
		hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); 
		if (SUCCEEDED(hres)) 
		{
			CHRconvToOs cto(pLinkFilePath, qtrue); // rmm9025
			WORD *wsz = cto.dataPtr();

			// Load the shortcut. 
			hres = ppf->Load(wsz, STGM_READ);
			if (SUCCEEDED(hres))
			{
				// Resolve the link. 
				hres = psl->Resolve(hwnd, SLR_ANY_MATCH);
				if (SUCCEEDED(hres)) 
				{ 
					// Get the path to the link target. 
					hres = psl->GetPath(szGotPath, MAX_PATH_FILEOPS, (WIN32_FIND_DATA *)&wfd, 0);  // rmm6231: return long path // rmm9025
                
					// Did I really get something useful
					hres = (_tcslen(szGotPath) > 0) ? 0 : -1;

					if ( SUCCEEDED(hres) ) 
					{ 
						// rmm6231: removed call to get description, which was failing, and removed
						// test on hRes when returning, which was clearing the resolved path.
						// Start rmm9025
						CHRconvFromOs cfoResPath(szGotPath);
						pResolvedPath.setChar(cfoResPath.dataPtr(), cfoResPath.len());
						// End rmm9025

						// What is resolved shortcut pointing to.
/*						pIsDir = isDirectory ( szGotPath );
						if (!pIsDir || pResolvedPath.length() <= 3 )
						{
							// Maybe its a Drive then
							pIsDrive = isDrive( szGotPath);
							if (pIsDrive) pIsDir = qfalse;
						} */
					} 
				} 
			} 
			// Release the pointer to the IPersistFile interface. 
			ppf->Release();
		} 
		// Release the pointer to the IShellLink interface. 
		psl->Release();	 
	}
	return hres;
#endif // MHUX001 for ifdef isunix
} 

static void addLine(qlong pWhat, EXTfldval& pPath, qlong pRetInfo, EXTqlist *pList, WIN32_FIND_DATA* pFindFile, strxxx *pFilter, FILEfilterMap* pFilterMap) // rmm9025 // rmm11107
{
	if ( !pFindFile ) return;

	qbool isDir = (qbool)(pFindFile->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!=0; SYSTEMTIME tm;

	if ( !isDir && pFilter )
	{
		str255 name(QOSTEXT(pFindFile->cFileName));
		if (!tqfFileOpsObj::canAddFile(name, pFilter, 0, pFilterMap))	// rmm11107
		{
			// if cannot add file then get out of here
			return; 
		}
	}

	if ( ( pWhat & cIncludeDirectories && isDir) || (pWhat & cIncludeFiles && !isDir) )
	{
		qshort col=1;
		EXTfldval cvalp;
		qlong line = pList->insertRow();
		for ( qlong loop=1; loop <= cInfoCalcDirectorySize; loop<<=1 ) // MHn0032 // MHn0483 // rmmvs8: qlong rather than qint // caa2019
		{
			if ( pRetInfo & loop )
			{
				pList->getColValRef( line , col, cvalp, qtrue );
				switch ( loop )
				{
					case cInfoResolvedFileName:	 // AE6723
					{
						str255 s;
						// rmm9025: Reworked:
						EXTfldval path;
						path = pPath;
						qlong sepPos = path.lastPos(FILE_SEPR_CHR);
						if (!sepPos)
							path.setEmpty(fftCharacter, dpDefault);
						else
							path.reduceCharLen(sepPos);
						path.concat( str255(QOSTEXT(pFindFile->cFileName)) );
						EXTfldval resolvedPath; qbool isDir = qfalse; qbool isDrive = qfalse;
						// Resolve the ShortCut.
						HWND mHWnd = 0;
						HRESULT res = ResolveIt(mHWnd, path, resolvedPath, isDir, isDrive );
						cvalp = resolvedPath;
						break;
					}
					case cInfoName:
					case cInfoName83:
					{
						// rmm9025: cAlternateFileName is short 8.3 name
						CHRconvFromOs cfoName((loop == cInfoName || pFindFile->cAlternateFileName[0] == '\0') ? pFindFile->cFileName : pFindFile->cAlternateFileName);
						cvalp.setChar(cfoName.dataPtr(), cfoName.len());
						break;
					}
					// AE4841 Starts
					case cInfoFullName:
					{
						// rmm9025: Reworked
						EXTfldval path;
						path = pPath;
						qlong sepPos = path.lastPos(FILE_SEPR_CHR);
						if (sepPos)
							path.reduceCharLen(sepPos);
						else
							path.setEmpty(fftCharacter, dpDefault);
						path.concat(str255(QOSTEXT(pFindFile->cFileName)));
						CHRconvToOs ctoPath(path, qtrue);
						qoschar pathBuffer[MAX_PATH_FILEOPS];
						qlong len = GetFullPathName(ctoPath.dataPtr(), MAX_PATH_FILEOPS, pathBuffer, NULL);
						sStorePathInFldval(pathBuffer, len, cvalp);
						break;
					}
					// AE4841 Ends
					case cInfoCreatorCode:
					{
						str255 creator; GetAssociatedApp(pPath, creator); // rmm9025
						cvalp.setChar( creator );
						break;
					}
					case cInfoTypeCode:
					{
						str255 ftype; LPTSTR lp;
						if ( lp = _tcsrchr(pFindFile->cFileName,'.') ) ftype = str255( QOSTEXT(++lp) );
						cvalp.setChar( ftype );
						break;
					}
					case cInfoReadOnly:			
					{
						cvalp.setBool( ((pFindFile->dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) ? 2 : 1 ); // rmm5354
						break;
					}
					case cInfoHidden:
					{
						cvalp.setBool( ((pFindFile->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0) ? 2 : 1 );		// rmm5354
						break;
					}
					case cInfoSize:
					case cInfoActualSize:
					{
						// Start rmm9010
						LARGE_INTEGER largeInt;
						#ifdef isunix
							largeInt.s.HighPart = pFindFile->nFileSizeHigh;
							largeInt.s.LowPart = pFindFile->nFileSizeLow;
						#else
							largeInt.HighPart = pFindFile->nFileSizeHigh;
							largeInt.LowPart = pFindFile->nFileSizeLow;
						#endif
						cvalp.setLong64(largeInt.QuadPart);
						// End rmm9010
						break;
					}
					// caa2019 start
					case cInfoCalcDirectorySize:
					{
						if (isDir)
						{
							LARGE_INTEGER largeInt; largeInt.QuadPart = 0;
							tqfFileOpsObj::getDirectorySize(largeInt, pPath);

							cvalp.setLong64(largeInt.QuadPart);
						}
						break;
					}
					// caa2019 end
					// MHn0032 begins.
					case cInfoResSize:
					case cInfoActualResSize:
						break;
					// MHn0032 ends.
					// MHn0483 begins
					case cInfoDirectory:			
					{
						cvalp.setBool( ((pFindFile->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) ? 2 : 1 ); // MHn0483
						break;
					}
					// MHn0483 ends
					case cInfoCreated:
					case cInfoModified:
					{
						datestamptype datetime;
						if ( loop == cInfoCreated ) 
						{
							FileTimeToLocalFileTime(&pFindFile->ftCreationTime, &pFindFile->ftCreationTime);
							FileTimeToSystemTime( &pFindFile->ftCreationTime, &tm);
						}
						else
						{
							FileTimeToLocalFileTime(&pFindFile->ftLastWriteTime, &pFindFile->ftLastWriteTime);
							FileTimeToSystemTime( &pFindFile->ftLastWriteTime, &tm);
						}
						datetime.mYear=(qshort)tm.wYear; datetime.mMonth=(char)tm.wMonth; datetime.mDay=(char)tm.wDay;
						datetime.mHour=(char)tm.wHour; datetime.mMin=(char)tm.wMinute; datetime.mSec=(char)tm.wSecond;
						datetime.mDateOk = datetime.mTimeOk = datetime.mSecOk = datetime.mHunOk = qtrue;
						cvalp.setDate ( datetime );
						break;
					}
				}			
				col++;
			}
		}
	}
}

// ###################### read and write entire file ######################
// mpmCarbon begins
struct tEntireHeader
{
	qlong		mType;
	qlong		mCreator;
	qlong		mDataForkSize;
};


static qlong sSwap( qlong lsrc )
{
	qlong ldest;
	qbyte* src = (qbyte*)&lsrc;
	qbyte* dest = (qbyte*)&ldest;
#ifdef ordermsb
	dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; dest[3] = src[3]; // AE5236
#else	
	dest[0] = src[3]; dest[1] = src[2]; dest[2] = src[1]; dest[3] = src[0];
#endif
	return ldest;
}


static OpsErr sWriteFile(EXTfldval &pPath, qlong pCount, const void *pBuffPtr)	// rmm64bitux // rmm9025
{
	if ( !pCount ) return 0;
	CHRconvToOs ctoFname(pPath, qtrue); // rmm9025
	HANDLE file = CreateFile( ctoFname.dataPtr(), //LPCTSTR lpFileName, // pointer to name of the file  
											GENERIC_WRITE, // DWORD dwDesiredAccess, // access (read-write) mode 
											0, //DWORD dwShareMode, // share mode  
											NULL, // LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes 
											CREATE_ALWAYS, // DWORD dwCreationDistribution, // how to create 
											FILE_ATTRIBUTE_NORMAL, // DWORD dwFlagsAndAttributes, // file attributes 
											NULL ); // HANDLE hTemplateFile // handle to file with attributes to copy 

	if ( INVALID_HANDLE_VALUE == file )
		return GetLastError();
	
	OpsErr theErr = 0; DWORD written;
	if ( !WriteFile( file, // handle to file to write to 
									(void *) pBuffPtr, // LPCVOID lpBuffer, // pointer to data to write to file // rmm64bitux
									pCount, // DWORD nNumberOfBytesToWrite, // number of bytes to write 
									&written, // LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written 
									NULL)// LPOVERLAPPED lpOverlapped // pointer to structure needed for overlapped I/O 
			)
				theErr = GetLastError();

	CloseHandle(file);
	return theErr;
}


static OpsErr sReadFile(EXTfldval &pPath, qHandle& pHan) // rmm9025
{
	pHan = 0;	// rmm8939a
	CHRconvToOs ctoFname(pPath, qtrue); // rmm9025
	HANDLE file = CreateFile( ctoFname.dataPtr(), //LPCTSTR lpFileName, // pointer to name of the file  
											GENERIC_READ, // DWORD dwDesiredAccess, // access (read-write) mode 
											0, //DWORD dwShareMode, // share mode  
											NULL, // LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes 
											OPEN_EXISTING, // DWORD dwCreationDistribution, // how to create 
											FILE_ATTRIBUTE_NORMAL, // DWORD dwFlagsAndAttributes, // file attributes 
											NULL ); // HANDLE hTemplateFile // handle to file with attributes to copy 

	if ( INVALID_HANDLE_VALUE == file )
		return GetLastError();

	WIN32_FIND_DATA finfo;
	HANDLE findHandle = FindFirstFile(ctoFname.dataPtr(), &finfo); // rmm8448
	// Start rmm8448
	if (INVALID_HANDLE_VALUE == findHandle)
	{
		OpsErr errCode = GetLastError();
		CloseHandle(file);
		return errCode;
	}
	// End rmm8448
	// Start rmm9010
	if (finfo.nFileSizeHigh || finfo.nFileSizeLow > (maxbinlen - sizeof(tEntireHeader)))
	{
		FindClose(findHandle);
		CloseHandle(file);
		return kFileOpsFileTooLarge;
	}
	// End rmm9010
	// rmm8939a: Allow space for tEntireHeader, so we do not copy the handle contents after returning to tqfFileOpsObj::readEntireFile
	OpsErr theErr = 0;
	DWORD bytesToRead = finfo.nFileSizeLow;
	DWORD bytesRead;
	pHan = HANglobalAlloc(bytesToRead + sizeof(tEntireHeader));
	if ( pHan )
	{
		qHandlePtr hp(pHan, sizeof(tEntireHeader));
		if ( ReadFile( file, // handle of file to read 
										*hp, //LPVOID lpBuffer, // address of buffer that receives data 
										bytesToRead, // DWORD nNumberOfBytesToRead, // number of bytes to read 
										&bytesRead,// LPDWORD lpNumberOfBytesRead, // address of number of bytes read 
										NULL ) // LPOVERLAPPED lpOverlapped // address of structure for data 
				)
		{
			hp.dataLen(bytesRead + sizeof(tEntireHeader));
		}
		else
		{
			theErr = GetLastError();
		}
	}
	else
	{
		theErr = kFileOpsOutOfMemory;
	}
	FindClose(findHandle); // rmm8448
	CloseHandle(file);
	if ( theErr != 0 && pHan )
	{
		HANglobalFree( pHan ); pHan = 0;
	}
	return theErr;
}


OpsErr	tqfFileOpsObj::readEntireFile(EXTfldval &pPath, qHandle &pData) // rmm9025
{
	// rmm8939a: sReadFile now leaves space for tEntireHeader at the start of the handle it returns
	OpsErr theErr = sReadFile(pPath, pData);
	if (!theErr)
	{
		qHandlePtr hp(pData, 0);
		tEntireHeader *hdr = (tEntireHeader *)*hp;
		MEMmovel("TEXT",&hdr->mType,4);
		MEMmovel("mdos",&hdr->mCreator,4);
		hdr->mDataForkSize = sSwap(hp.dataLen() - sizeof(tEntireHeader));
	}
	return GetOmnisError(theErr);
}

OpsErr	tqfFileOpsObj::writeEntireFile(EXTfldval& pPath, qHandle& pData) // rmm9025
{
	qHandlePtr hp(pData,0); qlong dataLen = hp.dataLen();
	OpsErr theErr = 0;
	if ( dataLen >= sizeof(tEntireHeader) )
	{
		qbyte* data = (qbyte*)(hp*0);
		tEntireHeader header; MEMmovel( data, &header, sizeof(tEntireHeader) );
		data += sizeof(tEntireHeader); dataLen -= sizeof(tEntireHeader);
		theErr = sWriteFile(pPath, sSwap(header.mDataForkSize), data);
	}
	return GetOmnisError(theErr);
}

// rmm10372: Platform dependent implementation of $selectfilesinsystemviewer
OpsErr tqfFileOpsObj::xSelectFilesInSystemViewer(EXTfldval& pFolderPath, EXTfldval& pFileNameList)
{
#ifndef isunix
	CHRconvToOs ctoFolderPath(pFolderPath);
	LPITEMIDLIST folder = ILCreateFromPath(ctoFolderPath.dataPtr());

	// Make sure folder path ends in a path separator
	if (pFolderPath.lastPos(EXTfile::pathSeparator()) < pFolderPath.getCharLen())
		pFolderPath.concat(EXTfile::pathSeparator());

	std::vector<LPITEMIDLIST> v;
	EXTqlist* fileNameList = pFileNameList.getList(qfalse);
	qlong lc = fileNameList->rowCnt();
	for (qlong ln = 1; ln <= lc; ++ln)
	{
		EXTfldval filePath;
		filePath = pFolderPath;
		EXTfldval fileName;
		fileNameList->getColValRef(ln, 1, fileName, qfalse);
		filePath.concat(fileName);

		CHRconvToOs ctoFilePath(filePath);
		v.push_back(ILCreateFromPath(ctoFilePath.dataPtr()));
	}
	delete fileNameList;

	SHOpenFolderAndSelectItems(folder, (UINT) v.size(), (LPCITEMIDLIST*)v.data(), 0);

	for (auto idl : v)
		ILFree(idl);
	ILFree(folder);
#endif
	return kFileOpsOK;
}

// Start rmm_mobile	
#if defined(iswin32) && !defined(isunix) && !defined(is64bit)	// rmm64bit6: CAB functions not available in 64 bit Omnis Studio
	// Make CAB callbacks
	FNFCIFILEPLACED(FOFCIfilePlaced)
	{
		// File has been committed to the cab file - there is nothing we need to do
		return 0;
	}
	
	FNFCIALLOC(FOFCIalloc)
	{
		// Allocate memory
		return malloc(cb);
	}

	FNFCIFREE(FOFCIfree)
	{
		// Free memory
		free(memory);
	}
	
	FNFCIOPEN(FOFCIopen)
	{
		// We use UTF-8 to pass file path names
		CHRconvFromUtf8 cfu((qbyte *) pszFile, strlen(pszFile) + 1);	// +1 includes null terminator
		CHRconvToOs cto(cfu.dataPtr(), cfu.len());
		int result = _topen(cto.dataPtr(), oflag, pmode);
    if (result == -1)
			*err = errno;

    return result;
	}
             
	FNFCIREAD(FOFCIread)
	{
    unsigned int result = (unsigned int) _read(hf, memory, cb);
    if (result != cb)
			*err = errno;
    return result;
	}
	
	FNFCIWRITE(FOFCIwrite)
	{
    unsigned int result = (unsigned int) _write(hf, memory, cb);
    if (result != cb)
			*err = errno;
    return result;
	}

	FNFCICLOSE(FOFCIclose)
	{
    int result = _close(hf);
    if (result != 0)
			*err = errno;
    return result;
	}
  
  FNFCISEEK(FOFCIseek)
  {
    long result = _lseek(hf, dist, seektype);
    if (result == -1)
			*err = errno;
    return result;
  }
  
  FNFCIDELETE(FOFCIdelete)
  {
		// We use UTF-8 to pass file path names
		CHRconvFromUtf8 cfu((qbyte *) pszFile, strlen(pszFile) + 1);	// +1 includes null terminator
		CHRconvToOs cto(cfu.dataPtr(), cfu.len());
    int result = _tremove(cto.dataPtr());
    if (result != 0)
			*err = errno;
    return result;
  }
  
  FNFCIGETTEMPFILE(FOFCIgetTempFile)
  {
		// Always use non-Unicode version of this function - it is only a temporary file
    char    *psz = _tempnam("", "__omniscab");
    if (psz && strlen(psz) < (unsigned) cbTempName)
    {
			strcpy(pszTempName, psz);
			free(psz);
			return TRUE;
    }
    if (psz) 
        free(psz);
    return FALSE;
  }
  
  FNFCIGETNEXTCABINET(FOFCIgetNextCabinet)
  {
		// Should never call this - we only create a single file
		return FALSE;
	}
	
	FNFCISTATUS(FOFCIprogress)
	{
		// Do nothing
		return 0;
	}
  
	FNFCIGETOPENINFO(FOFCIgetOpenInfo)
	{
		// Use a Win32 type handle to get file date/time using the Win32 APIs, even though the handle we
		// will be returning is of the type compatible with _topen

		// We use UTF-8 to pass file path names
		CHRconvFromUtf8 cfu((qbyte *) pszName, strlen(pszName) + 1);	// +1 includes null terminator
		CHRconvToOs ctoFileName(cfu.dataPtr(), cfu.len());

		HANDLE handle = CreateFile(ctoFileName.dataPtr(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
		if (INVALID_HANDLE_VALUE == handle)
			return -1;

		BY_HANDLE_FILE_INFORMATION	finfo;
		if (!GetFileInformationByHandle(handle, &finfo))
		{
			CloseHandle(handle);
			return -1;
		}
	   
		FILETIME filetime;
		FileTimeToLocalFileTime(&finfo.ftLastWriteTime, &filetime);
		FileTimeToDosDateTime(&filetime, pdate, ptime);

		DWORD attrs = GetFileAttributes(ctoFileName.dataPtr());
		if (attrs == 0xFFFFFFFF)
		{
			// failure
			*pattribs = 0;
		}
		else
		{
			// From the SDK example
			*pattribs = (int) (attrs & (_A_RDONLY | _A_SYSTEM | _A_HIDDEN | _A_ARCH));
			// Check for UTF-8 name
			int nameLen = strlen(pszName);
			for (int i = 0; i < nameLen; ++i)
			{
				if (pszName[i] & 0x80)
				{
					*pattribs |= _A_NAME_IS_UTF;
					break;
				}
			}
		}
		CloseHandle(handle);
		// Now Return handle using _topen
		return _topen(ctoFileName.dataPtr(), _O_RDONLY | _O_BINARY);
	}
  
	// Make CAB
	void tqfFileOpsObj::makeCab(str255 &pCabPath, EXTqlist *pFileList, strxxx &pErrorText)
	{
		// Get HFCI context for creating CAB file
		ERF fciErrorInfo;		
		CCAB cabParams;
		memset(&cabParams, 0, sizeof(CCAB));

		cabParams.cb = 0x7fffffff;							// No limit on size of CAB (in practice, we will only create fairly small CABs)
		cabParams.cbFolderThresh = 0x7fffffff;	// Don't worry about flushing folders - there is only one in our CAB
		// Do not reserve space for any extensions
		cabParams.cbReserveCFHeader = cabParams.cbReserveCFFolder = cabParams.cbReserveCFData = 0;
		cabParams.iCab = 1;											// Sequential cabinet number
		cabParams.iDisk = 0;										// Disk number
		cabParams.setID = 0x4F4D;							// Our own marker
		// Directory where CAB files are to be stored - we use UTF-8 for this
		CHRconvToUtf8 ctu(&pCabPath[1], pCabPath.length());
		qbyte *utfCabPath = ctu.dataPtr();
		qlong i;
		for (i = ctu.len() - 1; i > 0; --i)
		{
			if ('\\' == utfCabPath[i])
			{
				if ((i + 1) < CB_MAX_CAB_PATH)
				{
					memcpy(cabParams.szCabPath, utfCabPath, i + 1);	// Includes terminating backslash
					break;
				}
			}
		}
		if (!cabParams.szCabPath[0])
		{
			RESloadString(gInstLib, 10006, pErrorText);
			return;
		}
		// Name of output CAB file (again, UTF-8)
		if (i && i < (ctu.len() - 1))
		{
			qlong nameLen = ctu.len() - (i + 1);
			if ((nameLen + 1) > CB_MAX_CABINET_NAME)
			{
				RESloadString(gInstLib, 10007, pErrorText);
				return;
			}
			memcpy(cabParams.szCab, &utfCabPath[i + 1], nameLen);
		}		
		HFCI hfci = FCICreate(&fciErrorInfo, FOFCIfilePlaced, FOFCIalloc, FOFCIfree, FOFCIopen, FOFCIread, FOFCIwrite, FOFCIclose, FOFCIseek, FOFCIdelete, FOFCIgetTempFile, &cabParams, NULL);
		if (!hfci)
		{
			RESloadString(gInstLib, 10005, pErrorText);
			return;
		}
		// Add files to CAB file
		qlong fileCount = pFileList->rowCnt();
		qbool ok = qtrue;
		for (qlong fileNumber = 1; fileNumber <= fileCount; ++fileNumber)
		{
			EXTfldval fval;
			pFileList->getColValRef(fileNumber, 1, fval, qfalse);
			str255 path;
			fval.getChar(path);
			
			CHRconvToUtf8 ctu(path.cString(), path.length() + 1);	// +1 includes null terminator
			char *filePath = (char *) ctu.dataPtr();
			char *fileName;
			for (int j = strlen(filePath) - 1; j >= 0; --j)
			{
				if ('\\' == filePath[j])
				{
					fileName = &filePath[j + 1];
					break;
				}
			}
			if (!FCIAddFile(hfci, filePath, fileName, FALSE, FOFCIgetNextCabinet, FOFCIprogress, FOFCIgetOpenInfo, tcompTYPE_MSZIP))
			{
				ok = qfalse;
				RESloadString(gInstLib, 10008, pErrorText);			
				break;
			}			
		}
		
		if (ok)
		{
			// FCIFlushCabinet to finish with the file
			if (!FCIFlushCabinet(hfci, FALSE, FOFCIgetNextCabinet, FOFCIprogress))
			{
				RESloadString(gInstLib, 10010, pErrorText);						
			}
		}
		
		// Done - destroy context
		if (!FCIDestroy(hfci))
		{
			if (!pErrorText.length())
				RESloadString(gInstLib, 10009, pErrorText);			
		}
	}
	
	// Extract CAB callbacks
	FNALLOC(FOFDIalloc)
	{
		// Allocate memory
		return malloc(cb);
	}

	FNFREE(FOFDIfree)
	{
		// Free memory
		free(pv);
	}


	FNOPEN(FOFDIopen)
	{
		// We use UTF-8 to pass file path names
		CHRconvFromUtf8 cfu((qbyte *) pszFile, strlen(pszFile) + 1);	// +1 includes null terminator
		CHRconvToOs cto(cfu.dataPtr(), cfu.len());
		return _topen(cto.dataPtr(), oflag, pmode);
	}

	FNREAD(FOFDIread)
	{
		return _read(hf, pv, cb);
	}

	FNWRITE(FOFDIwrite)
	{
		return _write(hf, pv, cb);
	}

	FNCLOSE(FOFDIclose)
	{
		return _close(hf);
	}

	FNSEEK(FOFDIseek)
	{
		return _lseek(hf, dist, seektype);
	}

	FNFDINOTIFY(FOFDInotify)
	{
		switch (fdint)
		{
			case fdintCABINET_INFO: // general information about the cabinet
				return 0;
			case fdintPARTIAL_FILE: // first file in cabinet is continuation
				return 0;
			case fdintCOPY_FILE:	// file to be copied
			{
				char destination[MAX_PATH*4];
				int len = OMsnprintf(destination, sizeof(destination), "%s%s", (char *) pfdin->pv, pfdin->psz1);
				return FOFDIopen(destination, _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, _S_IREAD | _S_IWRITE);
			}
			case fdintCLOSE_FILE_INFO:	// close the file, set relevant info
			{
				char destination[MAX_PATH*4];
				int len = OMsnprintf(destination, sizeof(destination), "%s%s", (char *) pfdin->pv, pfdin->psz1);
				FOFDIclose(pfdin->hf);
				// Set date/time - needs Win32 handle for this
				CHRconvFromUtf8 cfu((qbyte *) destination, len + 1);	// +1 includes null terminator
				CHRconvToOs ctoDest(cfu.dataPtr(), cfu.len());
				HANDLE handle = CreateFile(ctoDest.dataPtr(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
				if (handle != INVALID_HANDLE_VALUE)
				{
					FILETIME datetime;
					if (DosDateTimeToFileTime(pfdin->date, pfdin->time, &datetime))
					{
						FILETIME filetime;
						if (LocalFileTimeToFileTime(&datetime, &filetime))
						{
							SetFileTime(handle, &filetime, NULL, &filetime);
						}
					}
					CloseHandle(handle);
				}
				// Mask out attribute bits other than readonly, hidden, system, and archive, since the other
				// attribute bits are reserved for use by the cabinet format.
				DWORD attrs = pfdin->attribs & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
				SetFileAttributes(ctoDest.dataPtr(), attrs);
				return TRUE;
			}
			case fdintNEXT_CABINET:	// file continued to next cabinet
				return 0;
		}

		return 0;
	}

	// Extract files from CAB
	void tqfFileOpsObj::extractCab(str255 &pCabPath, str255 &pExtractDir, strxxx &pErrorText)
	{
		// Get HFDI context
		ERF fdiErrorInfo;
		HFDI hfdi = FDICreate(FOFDIalloc, FOFDIfree, FOFDIopen, FOFDIread, FOFDIwrite, FOFDIclose, FOFDIseek, cpu80386, &fdiErrorInfo);
		if (!hfdi)
		{
			RESloadString(gInstLib, 10005, pErrorText);
			return;
		}
		rstrno errorResNum = 0;
		do
		{
			// Open file and make sure its a cabinet file
			CHRconvToOs ctoCabPath(pCabPath);
			{
				TCHAR *cabPath = ctoCabPath.dataPtr();
				int hf = _topen(cabPath, _O_BINARY | _O_RDONLY | _O_SEQUENTIAL, 0);
				if (-1 == hf)
				{
					errorResNum = 10011;
					break;
				}

				FDICABINETINFO	fdici;
				BOOL isCabinet = FDIIsCabinet(hfdi, hf, &fdici);
				_close(hf);
				if (!isCabinet)
				{
					errorResNum = 10012;
					break;
				}
			}
			
			{			
				CHRconvToUtf8 ctuPath(pCabPath.cString(), pCabPath.length() + 1);	// + 1 for null terminator
				char *utf8CabPath = (char *) ctuPath.dataPtr();
				char *p = strrchr(utf8CabPath, '\\');
				char cabName[MAX_PATH*4];
				char cabDir[MAX_PATH*4];
				if (!p)
				{
					// Should never really get here
					strcpy(cabName, utf8CabPath);
					cabDir[0] = 0;
				}
				else
				{
					strcpy(cabName, p + 1);
					memset(cabDir, 0, sizeof(cabDir));
					strncpy(cabDir, utf8CabPath, (int) (p - utf8CabPath) + 1);
				}
				if (pExtractDir[pExtractDir.length()] != '\\')
					pExtractDir.concat('\\');
				CHRconvToUtf8 ctuExtractDir(pExtractDir.cString(), pExtractDir.length() + 1);	// +1 for null terminator
				if (!FDICopy(hfdi, cabName, cabDir, 0, FOFDInotify, NULL, ctuExtractDir.dataPtr()))
				{
					errorResNum = 10013;
					break;
				}
			}
		} while (0);
		
		if (errorResNum)
			RESloadString(gInstLib, errorResNum, pErrorText);
				
		if (!FDIDestroy(hfdi))
		{
			if (!pErrorText.length())	
				RESloadString(gInstLib, 10009, pErrorText);			
		}
	}
#endif

// caa2019 start
void 	tqfFileOpsObj::getDirectorySize(LARGE_INTEGER& pDirSize, EXTfldval& pFilePath)
{

	str255 path(pFilePath.getChar());

#ifdef isunix
	str255 allFilesMask(QTEXT("/*"));
	str255 pathSep(QTEXT("/"));
#else
	str255 allFilesMask(QTEXT("\\*"));
	str255 pathSep(QTEXT("\\"));
#endif

	WIN32_FIND_DATA ffd;

	str255 searchPath(path);
	searchPath.concat(allFilesMask);

	CHRconvToOs cto(searchPath);

	HANDLE hSearch = FindFirstFile(cto.dataPtr(), &ffd);
	if (hSearch != INVALID_HANDLE_VALUE)
	{
		do
		{
			if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{

				if (strcmp((const char*)ffd.cFileName, ".") != 0 && strcmp((const char*)ffd.cFileName, "..") != 0)
				{
					str255 dirPath(path); dirPath.concat(pathSep, (str255)ffd.cFileName);

					CHRconvToOs dirCto(dirPath);

					EXTfldval directory;
					directory.setChar(dirPath);
					tqfFileOpsObj::getDirectorySize(pDirSize, directory);
				}
			}
			else
			{
#ifdef isunix
				pDirSize.s.HighPart += ffd.nFileSizeHigh;
				pDirSize.s.LowPart += ffd.nFileSizeLow;
#else
				pDirSize.HighPart += ffd.nFileSizeHigh;
				pDirSize.LowPart += ffd.nFileSizeLow;
#endif
			}
		} while (FindNextFile(hSearch, &ffd));
		FindClose(hSearch);
	}
}
// caa 2019 end
// End rmm_mobile
// End of file
