#include "common.h" #include #ifndef COMPCLEN_H #include "compclen.h" #endif #include #include #include #ifndef RESOURCE_H #include "resource.h" #endif #ifdef DEBUG #include #endif // DEBUG BOOL g_bSettingsChange = FALSE; const LPCTSTR g_NoCompressFiles[] = { TEXT("NTLDR"), TEXT("OSLOADER.EXE"), TEXT("PAGEFILE.SYS"), TEXT("NTDETECT.COM"), TEXT("EXPLORER.EXE"), }; LPCTSTR g_NoCompressExts[] = { TEXT(".PAL") }; extern HINSTANCE g_hDllModule; extern UINT incDllObjectCount(void); extern UINT decDllObjectCount(void); CCompCleanerClassFactory::CCompCleanerClassFactory() : m_cRef(1) { incDllObjectCount(); } CCompCleanerClassFactory::~CCompCleanerClassFactory() { decDllObjectCount(); } STDMETHODIMP CCompCleanerClassFactory::QueryInterface(REFIID riid, void **ppv) { *ppv = NULL; if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)) { *ppv = (IClassFactory *)this; AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CCompCleanerClassFactory::AddRef() { return ++m_cRef; } STDMETHODIMP_(ULONG) CCompCleanerClassFactory::Release() { if (--m_cRef) return m_cRef; delete this; return 0; } STDMETHODIMP CCompCleanerClassFactory::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void **ppvObj) { *ppvObj = NULL; if (pUnkOuter) { return CLASS_E_NOAGGREGATION; } HRESULT hr; CCompCleaner *pCompCleaner = new CCompCleaner(); if (pCompCleaner) { hr = pCompCleaner->QueryInterface (riid, ppvObj); pCompCleaner->Release(); } else { hr = E_OUTOFMEMORY; } return hr; } STDMETHODIMP CCompCleanerClassFactory::LockServer(BOOL fLock) { if (fLock) incDllObjectCount(); else decDllObjectCount(); return S_OK; } CCompCleaner::CCompCleaner() : m_cRef(1) { cbSpaceUsed.QuadPart = 0; cbSpaceFreed.QuadPart = 0; szVolume[0] = 0; szFolder[0] = 0; incDllObjectCount(); } CCompCleaner::~CCompCleaner() { // Free the list of directories FreeList(head); head = NULL; decDllObjectCount(); } STDMETHODIMP CCompCleaner::QueryInterface(REFIID riid, void **ppv) { *ppv = NULL; if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IEmptyVolumeCache) || IsEqualIID(riid, IID_IEmptyVolumeCache2)) { *ppv = (IEmptyVolumeCache2*) this; AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CCompCleaner::AddRef() { return ++m_cRef; } STDMETHODIMP_(ULONG) CCompCleaner::Release() { if (--m_cRef) return m_cRef; delete this; return 0; } // Initializes the Compression Cleaner and returns flags to the cache manager STDMETHODIMP CCompCleaner::Initialize(HKEY hRegKey, LPCWSTR pszVolume, LPWSTR *ppwszDisplayName, LPWSTR *ppwszDescription, DWORD *pdwFlags) { TCHAR szFileSystemName[MAX_PATH]; DWORD fFileSystemFlags; bPurged = FALSE; // // Allocate memory for the DisplayName string and load the string. // If the allocation fails, then we will return NULL which will cause // cleanmgr.exe to read the name from the registry. // if (*ppwszDisplayName = (LPWSTR)CoTaskMemAlloc(DISPLAYNAME_LENGTH * sizeof(WCHAR))) { LoadString(g_hDllModule, IDS_COMPCLEANER_DISP, *ppwszDisplayName, DISPLAYNAME_LENGTH); } // // Allocate memory for the Description string and load the string. // If the allocation fails, then we will return NULL which will cause // cleanmgr.exe to read the description from the registry. // if (*ppwszDescription = (LPWSTR)CoTaskMemAlloc(DESCRIPTION_LENGTH * sizeof(WCHAR))) { LoadString(g_hDllModule, IDS_COMPCLEANER_DESC, *ppwszDescription, DESCRIPTION_LENGTH); } // //If you want your cleaner to run only when the machine is dangerously low on //disk space then return S_FALSE unless the EVCF_OUTOFDISKSPACE flag is set. // //if (!(*pdwFlags & EVCF_OUTOFDISKSPACE)) //{ // return S_FALSE; //} if (*pdwFlags & EVCF_SETTINGSMODE) { bSettingsMode = TRUE; } else { bSettingsMode = FALSE; } //Tell the cache manager to disable this item by default *pdwFlags = 0; //Tell the Disk Cleanup Manager that we have a Settings button *pdwFlags |= EVCF_HASSETTINGS; // If we're in Settings mode no need to do all this other work // if (bSettingsMode) return S_OK; ftMinLastAccessTime.dwLowDateTime = 0; ftMinLastAccessTime.dwHighDateTime = 0; if (GetVolumeInformation(pszVolume, NULL, 0, NULL, NULL, &fFileSystemFlags, szFileSystemName, MAX_PATH) && (0 == lstrcmp(szFileSystemName, TEXT("NTFS"))) && (fFileSystemFlags & FS_FILE_COMPRESSION)) { lstrcpy(szFolder, pszVolume); // Calculate the last access date filetime CalcLADFileTime(); return S_OK; } return S_FALSE; } STDMETHODIMP CCompCleaner::InitializeEx(HKEY hRegKey, LPCWSTR pcwszVolume, LPCWSTR pcwszKeyName, LPWSTR *ppwszDisplayName, LPWSTR *ppwszDescription, LPWSTR *ppwszBtnText, DWORD *pdwFlags) { // Allocate memory for the ButtonText string and load the string. // If we can't allocate the memory, leave the pointer NULL. if (*ppwszBtnText = (LPWSTR)CoTaskMemAlloc(BUTTONTEXT_LENGTH * sizeof(WCHAR))) { LoadString(g_hDllModule, IDS_COMPCLEANER_BUTTON, *ppwszBtnText, BUTTONTEXT_LENGTH); } // // Now let the IEmptyVolumeCache version 1 Init function do the rest // return Initialize(hRegKey, pcwszVolume, ppwszDisplayName, ppwszDescription, pdwFlags); } // Returns the total amount of space that the compression cleaner can free. STDMETHODIMP CCompCleaner::GetSpaceUsed(DWORDLONG *pdwSpaceUsed, IEmptyVolumeCacheCallBack *picb) { cbSpaceUsed.QuadPart = 0; WalkFileSystem(picb, FALSE); picb->ScanProgress(cbSpaceUsed.QuadPart, EVCCBF_LASTNOTIFICATION, NULL); *pdwSpaceUsed = cbSpaceUsed.QuadPart; return S_OK; } // does the compression STDMETHODIMP CCompCleaner::Purge(DWORDLONG dwSpaceToFree, IEmptyVolumeCacheCallBack *picb) { bPurged = TRUE; // Compress the files WalkFileSystem(picb, TRUE); // Send the last notification to the cleanup manager picb->PurgeProgress(cbSpaceFreed.QuadPart, (cbSpaceUsed.QuadPart - cbSpaceFreed.QuadPart), EVCCBF_LASTNOTIFICATION, NULL); return S_OK; } /* ** Dialog box that displays all of the files that will be compressed by this cleaner. ** ** NOTE: Per the specification for the Compression Cleaner we are not ** providing the view files functionality. However, I will leave ** the framework in place just in case we want to use it. */ INT_PTR CALLBACK ViewFilesDlgProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam) { HWND hwndList; LV_ITEM lviItem; CLEANFILESTRUCT *pCleanFile; switch (Msg) { case WM_INITDIALOG: hwndList = GetDlgItem(hDlg, IDC_COMP_LIST); pCleanFile = (CLEANFILESTRUCT *)lParam; ListView_DeleteAllItems(hwndList); while (pCleanFile) { lviItem.mask = LVIF_TEXT | LVIF_IMAGE; lviItem.iSubItem = 0; lviItem.iItem = 0; // //Only show files // if (!pCleanFile->bDirectory) { lviItem.pszText = pCleanFile->file; ListView_InsertItem(hwndList, &lviItem); } pCleanFile = pCleanFile->pNext; lviItem.iItem++; } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hDlg, 0); break; } break; default: return FALSE; } return TRUE; } // Dialog box that displays the settings for this cleaner. INT_PTR CALLBACK SettingsDlgProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam) { HKEY hCompClenReg; // Handle to our registry path DWORD dwDisposition; // stuff for the reg calls DWORD dwByteCount; // Ditto DWORD dwNumDays = DEFAULT_DAYS; // Number of days setting from registry static UINT DaysIn; // Number of days initial setting UINT DaysOut; // Number of days final setting #ifdef DEBUG static CLEANFILESTRUCT *pCleanFile; // Pointer to our file list #endif // DEBUG switch(Msg) { case WM_INITDIALOG: #ifdef DEBUG pCleanFile = (CLEANFILESTRUCT *)lParam; #endif // DEBUG // // Set the range for the Days spin control (1 to 500) // SendDlgItemMessage(hDlg, IDC_COMP_SPIN, UDM_SETRANGE, 0, (LPARAM) MAKELONG ((short) MAX_DAYS, (short) MIN_DAYS)); // // Get the current user setting for # days and init // the spin control edit box // if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, COMPCLN_REGPATH, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hCompClenReg, &dwDisposition)) { dwByteCount = sizeof(dwNumDays); if (ERROR_SUCCESS == RegQueryValueEx(hCompClenReg, TEXT("Days"), NULL, NULL, (LPBYTE) &dwNumDays, &dwByteCount)) { // // Got day count from registry, make sure it's // not too big or too small. // if (dwNumDays > MAX_DAYS) dwNumDays = MAX_DAYS; if (dwNumDays < MIN_DAYS) dwNumDays = MIN_DAYS; SetDlgItemInt(hDlg, IDC_COMP_EDIT, dwNumDays, FALSE); } else { // // Failed to get the day count from the registry // so just use the default. // SetDlgItemInt(hDlg, IDC_COMP_EDIT, DEFAULT_DAYS, FALSE); } } else { // // Failed to get the day count from the registry // so just use the default. // SetDlgItemInt(hDlg, IDC_COMP_EDIT, DEFAULT_DAYS, FALSE); } RegCloseKey(hCompClenReg); // Track the initial setting so we can figure out // if the user has changed the setting on the way // out. DaysIn = GetDlgItemInt(hDlg, IDC_COMP_EDIT, NULL, FALSE); break; case WM_COMMAND: switch (LOWORD(wParam)) { #ifdef DEBUG case IDC_VIEW: DialogBoxParam(g_hDllModule, MAKEINTRESOURCE(IDD_COMP_VIEW), hDlg, ViewFilesDlgProc, (LPARAM)pCleanFile); break; #endif // DEBUG case IDOK: // // Get the current spin control value and write the // setting to the registry. // DaysOut = GetDlgItemInt(hDlg, IDC_COMP_EDIT, NULL, FALSE); if (DaysOut > MAX_DAYS) DaysOut = MAX_DAYS; if (DaysOut < MIN_DAYS) DaysOut = MIN_DAYS; if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, COMPCLN_REGPATH, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hCompClenReg, &dwDisposition)) { dwNumDays = (DWORD)DaysOut; RegSetValueEx(hCompClenReg, TEXT("Days"), 0, REG_DWORD, (LPBYTE) &dwNumDays, sizeof(dwNumDays)); RegCloseKey(hCompClenReg); } // Don't care if this failed -- what can we // do about it anyway... // If the user has changed the setting we need // to recalculate the list of files. if (DaysIn != DaysOut) { g_bSettingsChange = TRUE; } // Fall thru to IDCANCEL case IDCANCEL: EndDialog(hDlg, 0); break; } break; default: return FALSE; } return TRUE; } STDMETHODIMP CCompCleaner::ShowProperties(HWND hwnd) { g_bSettingsChange = FALSE; DialogBoxParam(g_hDllModule, MAKEINTRESOURCE(IDD_COMP_SETTINGS), hwnd, SettingsDlgProc, (LPARAM)head); // // If the settings have changed we need to recalculate the // LAD Filetime. // if (g_bSettingsChange) { CalcLADFileTime(); return S_OK; // Tell CleanMgr that settings have changed. } else { return S_FALSE; // Tell CleanMgr no settings changed. } return S_OK; // Shouldn't hit this but just in case. } // Deactivates the cleaner...this basically does nothing. STDMETHODIMP CCompCleaner::Deactivate(DWORD *pdwFlags) { *pdwFlags = 0; return S_OK; } /* ** checks if the file is a specified number of days ** old. If the file has not been accessed in the ** specified number of days then it can safely be ** deleted. If the file has been accessed in that ** number of days then the file will not be deleted. ** ** Notes; ** Mod Log: Created by Jason Cobb (7/97) ** Adapted for Compression Cleaner by DSchott (6/98) */ BOOL CCompCleaner::LastAccessisOK(FILETIME ftFileLastAccess) { //Is the last access FILETIME for this file less than the current //FILETIME minus the number of specified days? return (CompareFileTime(&ftFileLastAccess, &ftMinLastAccessTime) == -1); } // This function checks if the file is in the g_NoCompressFiles file list. // If it is, returns TRUE, else FALSE. BOOL IsDontCompressFile(LPCTSTR lpFullPath) { LPCTSTR lpFile = PathFindFileName(lpFullPath); if (lpFile) { for (int i = 0; i < ARRAYSIZE(g_NoCompressFiles); i++) { if (!lstrcmpi(lpFile, g_NoCompressFiles[i])) { MiDebugMsg((0, TEXT("File is in No Compress list: %s"), lpFile)); return TRUE; } } LPCTSTR lpExt = PathFindExtension(lpFile); if (lpExt) { for (int i = 0; i < ARRAYSIZE(g_NoCompressExts); i++) { if (!lstrcmpi(lpExt, g_NoCompressExts[i])) { MiDebugMsg((0, TEXT("File has No Compress extension: %s"), lpFile)); return TRUE; } } } } return FALSE; // If we made it here the file must be OK to compress. } /* ** checks if a file is open by doing a CreateFile ** with fdwShareMode of 0. If GetLastError() retuns ** ERROR_SHARING_VIOLATION then this function retuns TRUE because ** someone has the file open. Otherwise this function retuns false. ** ** Notes; ** Mod Log: Created by Jason Cobb (7/97) ** Adapted for Compression Cleaner by DSchott (6/98) **------------------------------------------------------------------------------ */ BOOL IsFileOpen(LPTSTR lpFile, DWORD dwAttributes, FILETIME *lpftFileLastAccess) { BOOL bRet = FALSE; #if 0 // Need to see if we can open file with WRITE access -- if we // can't we can't compress it. Of course if the file has R/O // attribute then we won't be able to open for WRITE. So, // we need to remove the R/O attribute long enough to try // opening the file then restore the original attributes. if (dwAttributes & FILE_ATTRIBUTE_READONLY) { SetFileAttributes(lpFile, FILE_ATTRIBUTE_NORMAL); } SetLastError(0); HANDLE hFile = CreateFile(lpFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { DWORD dwResult = GetLastError(); if ((ERROR_SHARING_VIOLATION == dwResult) || (ERROR_ACCESS_DENIED == dwResult)) { bRet = TRUE; } } else { SetFileTime(hFile, NULL, lpftFileLastAccess, NULL); CloseHandle(hFile); } if (dwAttributes & FILE_ATTRIBUTE_READONLY) SetFileAttributes(lpFile, dwAttributes); #endif return bRet; } /* ** ** Purpose: This function provides a common entry point for ** searching for files to compress and then compressing them ** ** Notes; ** Mod Log: Created by Bret Anderson (1/01) ** **------------------------------------------------------------------------------ */ void CCompCleaner::WalkFileSystem(IEmptyVolumeCacheCallBack *picb, BOOL bCompress) { MiDebugMsg((0, TEXT("CCompCleaner::WalkFileSystem"))); cbSpaceUsed.QuadPart = 0; if (!bCompress) { // //Walk all of the folders in the folders list scanning for disk space. // for (LPTSTR lpSingleFolder = szFolder; *lpSingleFolder; lpSingleFolder += lstrlen(lpSingleFolder) + 1) WalkForUsedSpace(lpSingleFolder, picb, bCompress, 0); } else { // // Walk through the linked list of directories compressing the necessary files // CLEANFILESTRUCT *pCompDir = head; while (pCompDir) { WalkForUsedSpace(pCompDir->file, picb, bCompress, 0); pCompDir = pCompDir->pNext; } } return; } /* ** Purpose: This function gets the current last access days ** setting from the registry and calculates the magic ** filetime we're looking for when searching for files ** to compress. ** ** Notes; ** Mod Log: Created by David Schott (7/98) */ void CCompCleaner::CalcLADFileTime() { HKEY hCompClenReg = NULL; // Handle to our registry path DWORD dwDisposition; // stuff for the reg calls DWORD dwByteCount; // Ditto DWORD dwDaysLastAccessed = 0; // Day count from the registry setting MiDebugMsg((0, TEXT("CCompCleaner::CalcLADFileTime"))); // // Get the DaysLastAccessed value from the registry. // dwDaysLastAccessed = DEFAULT_DAYS; if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, COMPCLN_REGPATH, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hCompClenReg, &dwDisposition)) { dwByteCount = sizeof(dwDaysLastAccessed); RegQueryValueEx(hCompClenReg, TEXT("Days"), NULL, NULL, (LPBYTE) &dwDaysLastAccessed, &dwByteCount); RegCloseKey(hCompClenReg); } // // Verify LD setting is within range // if (dwDaysLastAccessed > MAX_DAYS) dwDaysLastAccessed = MAX_DAYS; if (dwDaysLastAccessed < MIN_DAYS) dwDaysLastAccessed = MIN_DAYS; // //Determine the LastAccessedTime // if (dwDaysLastAccessed != 0) { ULARGE_INTEGER ulTemp, ulLastAccessTime; FILETIME ft; //Determine the number of days in 100ns units ulTemp.LowPart = FILETIME_HOUR_LOW; ulTemp.HighPart = FILETIME_HOUR_HIGH; ulTemp.QuadPart *= dwDaysLastAccessed; //Get the current FILETIME GetSystemTimeAsFileTime(&ft); ulLastAccessTime.LowPart = ft.dwLowDateTime; ulLastAccessTime.HighPart = ft.dwHighDateTime; //Subtract the Last Access number of days (in 100ns units) from //the current system time. ulLastAccessTime.QuadPart -= ulTemp.QuadPart; //Save this minimal Last Access time in the FILETIME member variable //ftMinLastAccessTime. ftMinLastAccessTime.dwLowDateTime = ulLastAccessTime.LowPart; ftMinLastAccessTime.dwHighDateTime = ulLastAccessTime.HighPart; } } /* ** Purpose: This function will walk the specified directory and increment ** the member variable to indicate how much disk space these files ** are taking or it will perform the action of compressing the files ** if the bCompress variable is set. ** It will look at the dwFlags member variable to determine if it ** needs to recursively walk the tree or not. ** We no longer want to store a linked list of all files to compress ** due to extreme memory usage on large filesystems. This means ** we will walk through all the files on the system twice. ** Notes; ** Mod Log: Created by Jason Cobb (2/97) ** Adapted for Compression Cleaner by DSchott (6/98) */ BOOL CCompCleaner::WalkForUsedSpace(LPCTSTR lpPath, IEmptyVolumeCacheCallBack *picb, BOOL bCompress, int depth) { BOOL bRet = TRUE; BOOL bFind = TRUE; WIN32_FIND_DATA wd; ULARGE_INTEGER dwFileSize; static DWORD dwCount = 0; TCHAR szFindPath[MAX_PATH], szAddFile[MAX_PATH]; if (PathCombine(szFindPath, lpPath, TEXT("*.*"))) { BOOL bFolderFound = FALSE; bFind = TRUE; HANDLE hFind = FindFirstFile(szFindPath, &wd); while (hFind != INVALID_HANDLE_VALUE && bFind) { if (!PathCombine(szAddFile, lpPath, wd.cFileName)) { // Failure here means the file name is too long, just ignore that file continue; } if (wd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dwFileSize.HighPart = 0; dwFileSize.LowPart = 0; bFolderFound = TRUE; } else if ((IsFileOpen(szAddFile, wd.dwFileAttributes, &wd.ftLastAccessTime) == FALSE) && (!(wd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)) && (!(wd.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) && (!(wd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE)) && (LastAccessisOK(wd.ftLastAccessTime)) && (!IsDontCompressFile(szAddFile))) { dwFileSize.HighPart = wd.nFileSizeHigh; dwFileSize.LowPart = wd.nFileSizeLow; if (bCompress) { if (!CompressFile(picb, szAddFile, dwFileSize)) { bRet = FALSE; bFind = FALSE; break; } } else { cbSpaceUsed.QuadPart += (dwFileSize.QuadPart * 4 / 10); } } // CallBack the cleanup Manager to update the UI if ((dwCount++ % 10) == 0 && !bCompress) { if (picb && picb->ScanProgress(cbSpaceUsed.QuadPart, 0, NULL) == E_ABORT) { // //User aborted // bFind = FALSE; bRet = FALSE; break; } } bFind = FindNextFile(hFind, &wd); } FindClose(hFind); if (bRet && bFolderFound) { // //Recurse through all of the directories // if (PathCombine(szFindPath, lpPath, TEXT("*.*"))) { bFind = TRUE; HANDLE hFind = FindFirstFile(szFindPath, &wd); while (hFind != INVALID_HANDLE_VALUE && bFind) { if ((wd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (lstrcmp(wd.cFileName, TEXT(".")) != 0) && (lstrcmp(wd.cFileName, TEXT("..")) != 0)) { ULARGE_INTEGER cbSpaceBefore; cbSpaceBefore.QuadPart = cbSpaceUsed.QuadPart; PathCombine(szAddFile, lpPath, wd.cFileName); if (WalkForUsedSpace(szAddFile, picb, bCompress, depth + 1) == FALSE) { // User canceled bFind = FALSE; bRet = FALSE; break; } // Tag this directory for compression // We only want to tag directories that are in the root // otherwise we'll end up with a very large data structure if (cbSpaceBefore.QuadPart != cbSpaceUsed.QuadPart && depth == 0 && !bCompress) { AddDirToList(szAddFile); } } bFind = FindNextFile(hFind, &wd); } FindClose(hFind); } } } return bRet; } // Adds a directory to the linked list of directories. BOOL CCompCleaner::AddDirToList(LPCTSTR lpFile) { BOOL bRet = TRUE; CLEANFILESTRUCT *pNew = (CLEANFILESTRUCT *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pNew)); if (pNew == NULL) { MiDebugMsg((0, TEXT("CCompCleaner::AddDirToList -> ERROR HeapAlloc() failed with error %d"), GetLastError())); return FALSE; } lstrcpy(pNew->file, lpFile); if (head) pNew->pNext = head; else pNew->pNext = NULL; head = pNew; return bRet; } void CCompCleaner::FreeList(CLEANFILESTRUCT *pCleanFile) { if (pCleanFile == NULL) return; if (pCleanFile->pNext) FreeList(pCleanFile->pNext); HeapFree(GetProcessHeap(), 0, pCleanFile); } // Compresses the specified file BOOL CCompCleaner::CompressFile(IEmptyVolumeCacheCallBack *picb, LPCTSTR lpFile, ULARGE_INTEGER filesize) { ULARGE_INTEGER ulCompressedSize; ulCompressedSize.QuadPart = filesize.QuadPart; // If the file is read only, we need to remove the // R/O attribute long enough to compress the file. BOOL bFileWasRO = FALSE; DWORD dwAttributes = GetFileAttributes(lpFile); if ((0xFFFFFFFF != dwAttributes) && (dwAttributes & FILE_ATTRIBUTE_READONLY)) { bFileWasRO = TRUE; SetFileAttributes(lpFile, FILE_ATTRIBUTE_NORMAL); } HANDLE hFile = CreateFile(lpFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE != hFile) { USHORT InBuffer = COMPRESSION_FORMAT_DEFAULT; DWORD dwBytesReturned = 0; if (DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, &InBuffer, sizeof(InBuffer), NULL, 0, &dwBytesReturned, NULL)) { // Get the compressed file size so we can figure out // how much space we gained by compressing. ulCompressedSize.LowPart = GetCompressedFileSize(lpFile, &ulCompressedSize.HighPart); } CloseHandle(hFile); } // Restore the file attributes if needed if (bFileWasRO) SetFileAttributes(lpFile, dwAttributes); // Adjust the cbSpaceFreed cbSpaceFreed.QuadPart = cbSpaceFreed.QuadPart + (filesize.QuadPart - ulCompressedSize.QuadPart); // Call back the cleanup manager to update the progress bar if (picb->PurgeProgress(cbSpaceFreed.QuadPart, (cbSpaceUsed.QuadPart - cbSpaceFreed.QuadPart), 0, NULL) == E_ABORT) { // User aborted so stop compressing files MiDebugMsg((0, TEXT("CCompCleaner::PurgeFiles User abort"))); return FALSE; } return TRUE; }