/************************************************************************
*
* Description : File related utility functions.
*
* (c) Copyright 2000-2008 by Anna-Jayne Metcalfe (anna@riverblade.co.uk)
* and Beth Mackenzie (beth@riverblade.co.uk) / Riverblade Limited
*
* Licence Terms:
*
* This code may be freely reused, subject to the licence terms below.
* Please do let us know of any bugs you find or improvements you make,
* so that we can pass them on to the rest of the development community.
*
* This code is free software; you can redistribute it and/or
* modify it under the terms of the Code Project Open License (CPOL)
* version 1.0 (http://www.codeproject.com/info/cpol10.aspx).
*
* This code 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
* Code Project Open Licence for further details.
*
************************************************************************
* $Archive: /Projects/AddIns/Visual Lint/Development/Libraries/Utils/src/FileUtils.cpp $
* $Revision: 10 $
* $Date: 28/03/08 16:27 $
* $Author: Anna $
*
* $Nokeywords: $
************************************************************************/
/// \file
/// \brief File utility functions implementation.
#include "StdAfx.h"
#ifndef __ATLTIME_H__
#pragma message(__FILE__" : For faster compilation, #include <atltime.h> in stdafx.h")
#include <atltime.h>
#endif
#ifndef _INC_COMDEF
#pragma message(__FILE__" : For faster compilation, #include <comdef.h> in stdafx.h")
#include <comdef.h> // For _com_error
#endif
#include "StringUtils.h"
#include "FileUtils.h"
namespace Riverblade
{
namespace Libraries
{
/////////////////////////////////////////////////////////////////////////////
// Helper functions
/// \brief Determine whether the given file exists.
///
/// \param sPathName The pathname of the file.
/// \return \em true if the file exists; \em false otherwise.
///
bool Utils::FileExists(const CString& sPathName)
{
if (!sPathName.IsEmpty() )
{
WIN32_FIND_DATA FileData;
// Use FindFirstFile() to see if the file exists. Note that if a valid
// handle is returned (i.e. the file is found) the handle must be closed
// using FindClose() or else a handle leak will result [IMS ID 18]
const HANDLE hFind = ::FindFirstFile(sPathName, &FileData);
if (INVALID_HANDLE_VALUE != hFind)
{
ATLVERIFY(::FindClose(hFind) );
return true;
}
}
return false;
}
/// \brief Determine whether the given folder exists.
///
/// \param sFolderPath The pathname of the folder.
/// \return \em true if the folder exists; \em false otherwise.
///
bool Utils::FolderExists(const CString& sFolderPath)
{
if (!sFolderPath.IsEmpty() )
{
const DWORD dwAttribs = ::GetFileAttributes(sFolderPath);
if ( (INVALID_FILE_ATTRIBUTES != dwAttribs) && (FILE_ATTRIBUTE_DIRECTORY == (dwAttribs & FILE_ATTRIBUTE_DIRECTORY) ) )
{
return true;
}
}
return false;
}
/// \brief Return the last modification time of the given file.
///
/// \param sPathName The pathname of the file whose modification time should be tested against.
/// \param pFileTime A pointer to an LPFILETIME structure which will receive the modification
/// time of the file.
/// \return \em true if the operation succeeded; \em false otherwise.
///
bool Utils::GetFileLastWriteTime(const CString& sPathName, LPFILETIME pFileTime)
{
BOOL bResult = false;
const HANDLE hFile = ::CreateFile(sPathName,
GENERIC_READ, // Desired Access
FILE_SHARE_READ, // Share Mode
NULL, // Security Attributes
OPEN_EXISTING, // Creation Disposition
FILE_ATTRIBUTE_NORMAL, // File AndAttributes,
NULL); // Template File
if (INVALID_HANDLE_VALUE != hFile)
{
bResult = ::GetFileTime(hFile,
NULL, // Creation time
NULL, // Last access time
pFileTime); // Last write time
ATLVERIFY(::CloseHandle(hFile) );
}
#ifdef _DEBUG
if (!bResult)
{
const DWORD dwError = ::GetLastError();
//lint -save -e437 (Warning -- Passing struct 'CStringT' to ellipsis)
ATLTRACE( _T("WARNING: GetFileLastWriteTime(%s) failed with error code %x\n"), sPathName, dwError);
//lint -restore
}
#endif
//lint -save -e731 (Info -- Boolean argument to equal/not equal)
return (false != bResult);
//lint -restore
}
/// \brief Determine whether the given file has a modification date which is newer than the given time.
///
/// \param sPathName The pathname of the file whose modification time should be tested against.
/// \param pFileTimeCompare A pointer to an LPFILETIME structure holding the timestamp to compare against.
/// \return \em true if the file has been modified since the time given in pFileTimeCompare;
/// \em false otherwise.
///
bool Utils::IsFileWriteTimeNewerThan(const CString& sPathName, const LPFILETIME pFileTimeCompare)
{
if (FileExists(sPathName) )
{
CFileTime timeFileModified;
ATLVERIFY(GetFileLastWriteTime(sPathName, &timeFileModified) );
if (CompareFileTime(&timeFileModified, pFileTimeCompare) > 0)
{
// The object file is newer than the results file
return true;
}
}
return false;
} //lint !e818 (Information -- Pointer parameter 'pFileTimeCompare' could be declared as pointing to const)
/// \brief Generate a pathname for a temporary file in the specified folder.
///
/// \param pszPrefixString The prefix string which should be used in the filename.
/// \param pszFolder The folder within which the file will be created.
/// \param uUnique Unsigned integer to be used in creating the temporary file name.
/// If uUnique is zero, the function attempts to form a unique file name
/// using the current system time. If the file already exists, the number
/// is increased by one and the functions tests if this file already exists.
/// This continues until a unique filename is found; the function creates
/// a file by that name and closes it.
///
/// Note that the function does not attempt to verify the uniqueness of the
/// file name when uUnique is nonzero.
/// \return The pathname of the temporary file.
///
CString Utils::GetTempFilePathName(LPCTSTR const pszPrefixString, LPCTSTR const pszFolder /*= NULL*/, UINT uUnique /*= 0*/)
{
CString sFolder(pszFolder);
if (sFolder.IsEmpty() )
{
TCHAR szTempFolder[_MAX_PATH + 1];
(void)::GetTempPath(_MAX_PATH, szTempFolder);
sFolder = szTempFolder;
}
ATLASSERT(FolderExists(sFolder) );
CString sTempPathName;
LPTSTR const pszTempPathName = sTempPathName.GetBuffer(_MAX_PATH + 1);
(void)::GetTempFileName(sFolder,
pszPrefixString,
uUnique,
pszTempPathName);
sTempPathName.ReleaseBuffer();
return sTempPathName;
}
/// \brief Read the contents of the given text file to a string array.
///
/// \param sPathName The pathname of the file.
/// \param rarrayText A reference to an array object which will receive a copy of the contents
/// from the file if the operation succeeds.
/// \param puLength A pointer to a ULONGLONG which will receive the size of the file on completion.
/// \return \em S_OK if the file was read successfully.
///
HRESULT Utils::ReadTextFile(const CString& sPathName, CAtlArray<CString>& rarrayText, ULONGLONG* puLength /*= NULL*/)
{
ATLVERIFY(rarrayText.SetCount(0, 100) );
CString sText;
const HRESULT hr = ReadTextFile(sPathName, sText, puLength);
if (SUCCEEDED(hr) )
{
(void)StrToArray(sText, rarrayText);
}
return hr;
}
/// \brief Read the contents of the given text file to a string.
///
/// \param sPathName The pathname of the file.
/// \param rsText A reference to a CString object which will receive a copy of the text
/// from the file if the operation succeeds.
/// \param puLength A pointer to a ULONGLONG which will receive the size of the file on completion.
/// \return \em S_OK if the file was read successfully.
///
HRESULT Utils::ReadTextFile(const CString& sPathName, CString& rsText, ULONGLONG* puLength /*= NULL*/)
{
rsText = _T("");
CAtlFile file;
const HRESULT hr = file.Create(sPathName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
ATLASSERT(SUCCEEDED(hr) );
if (SUCCEEDED(hr) )
{
ULONGLONG uLen = 0;
ATLVERIFY(SUCCEEDED(file.GetSize(uLen) ) );
CStringA sRawDataA;
LPVOID const pBuffer = static_cast<LPVOID>(sRawDataA.GetBuffer(static_cast<int>(uLen) + 1) );
DWORD dwRead = 0;
ATLVERIFY(SUCCEEDED(file.Read(pBuffer, static_cast<DWORD>(uLen), dwRead) ) );
sRawDataA.ReleaseBuffer(static_cast<int>(dwRead) );
file.Close();
rsText = CA2CT(sRawDataA);
if (NULL != puLength)
{
*puLength = uLen;
}
}
return hr;
}
/// \brief Write a text file containing the given text.
///
/// \param sPathName The pathname of the file.
/// \param arrayText The text to write.
/// \param sDelimiter The end of line marker to use (default cr + lf)
/// \param psErrorMsg A pointer to a CString object which will receive error information in the event of failure. Optional.
/// \return \em S_OK if the file was written successfully.
///
HRESULT Utils::WriteTextFile(const CString& sPathName,
const CAtlArray<CString>& arrayText,
const CString& sDelimiter /*= _T("\r\n")*/,
CString* psErrorMsg /*= NULL*/)
{
const CString sText = ArrayToStr(arrayText, sDelimiter);
return WriteTextFile(sPathName, sText, psErrorMsg);
}
/// \brief Write a text file containing the given text.
///
/// \param sPathName The pathname of the file.
/// \param sText The text to write.
/// \param psErrorMsg A pointer to a CString object which will receive error information in the event of failure. Optional.
/// \return \em S_OK if the file was written successfully.
///
HRESULT Utils::WriteTextFile(const CString& sPathName, const CString& sText, CString* psErrorMsg /*= NULL*/)
{
HRESULT hr = S_FALSE;
if (!sText.IsEmpty() )
{
CAtlFile file;
hr = file.Create(sPathName, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS);
if (SUCCEEDED(hr) )
{
ATLVERIFY(SUCCEEDED(file.Write( CT2CA(sText),
static_cast<DWORD>(sText.GetLength() ) ) ) );
file.Close();
}
else
{
// Find out why the file could not be written
_com_error e(static_cast<HRESULT>(::GetLastError() ) );
hr = e.Error();
ATLTRACE( _T("Unable to create output file. Error code 0x%X (%s)\n"), e.Error(), e.ErrorMessage() );
if (NULL != psErrorMsg)
{
*psErrorMsg = e.ErrorMessage();
}
}
}
return hr;
}
/// \brief Recursively search for the first instance of the specified file.
///
/// \param sStartPath The folder path at which the search should start.
/// \param sMask The file mask to search for (wildcards are allowed).
/// \return The pathname of the first occurance of the specified file.
///
CString Utils::RecursiveFindFile(const CString& sStartPath, const CString& sMask)
{
CAtlArray<CString> arrayFoundPathNames;
if (RecursiveFindFile(sStartPath, sMask, arrayFoundPathNames, true) )
{
return arrayFoundPathNames[0];
}
return _T("");
}
/// \brief Recursively search for the specified file.
///
/// \param sStartPath The folder path at which the search should start.
/// \param sMask The file mask to search for (wildcards are allowed).
/// \param rarrayFoundPathNames A reference to an array of CString objects which will receive
/// the pathnames of the found files.
/// \param bStopWhenFound \em true if the search will stop when the first file is found;
/// \em false otherwise.
/// \return \em true if the specified file was found; \em false otherwise.
///
bool Utils::RecursiveFindFile(const CString& sStartPath, const CString& sMask, CAtlArray<CString>& rarrayFoundPathNames, bool bStopWhenFound /*= false*/)
{
WIN32_FIND_DATA ffd;
// 1st Part: find the files
CPath path(sStartPath);
ATLVERIFY(path.Append(sMask) );
HANDLE hFind = ::FindFirstFileEx( static_cast<LPCTSTR>(path), // Pathname
FindExInfoStandard, // information Level
&ffd, // information buffer
FindExSearchNameMatch, //
NULL, // search Criteria
0 ); // reserved
if (INVALID_HANDLE_VALUE == hFind)
{
const DWORD le = ::GetLastError();
if (ERROR_FILE_NOT_FOUND != le)
{
ATLTRACE(_T("Utils::RecursiveFindFile(): Invalid File Handle, err code: %d\n"), le);
return false;
}
}
if (INVALID_HANDLE_VALUE != hFind)
{
do
{
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
continue;
rarrayFoundPathNames.Add(sStartPath + _T("\\") + ffd.cFileName);
if (bStopWhenFound)
{
ATLVERIFY(::FindClose(hFind) );
return true;
}
}
while (::FindNextFile(hFind,&ffd) );
ATLVERIFY(::FindClose(hFind) );
}
// 2nd Part: Find the directories
CPath pathDirs(sStartPath);
ATLVERIFY(pathDirs.Append(_T("\\*") ) );
hFind = ::FindFirstFileEx( static_cast<LPCTSTR>(pathDirs), // File Name
FindExInfoStandard, // information Level
&ffd, // information buffer
FindExSearchLimitToDirectories, // only Directories
NULL, // search Criteria
0); // reserved
if (INVALID_HANDLE_VALUE == hFind)
{
ATLTRACE(_T("Utils::RecursiveFindFile(): Invalid File Handle, err code: %d\n"), ::GetLastError() );
return false;
}
do
{
const CString dirName = ffd.cFileName;
if ( (dirName == _T(".") ) || (dirName == _T("..") ) )
continue;
if (! (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
continue;
CPath pathRecurse(sStartPath);
ATLVERIFY(pathRecurse.Append(ffd.cFileName) );
if (RecursiveFindFile(static_cast<LPCTSTR>(pathRecurse), sMask, rarrayFoundPathNames, bStopWhenFound) && bStopWhenFound)
{
return true;
}
}
while (::FindNextFile(hFind,&ffd));
ATLVERIFY(::FindClose(hFind) );
return false;
}
}; // namespace Libraries
}; // namespace Riverblade