Files
Katayama Hirofumi MZ 2c80eeec5e [ZIPFLDR] CZipCreator: Use file mapping (#8754)
I dislike slow system. Support large files.
JIRA issue: CORE-20466
2026-03-28 22:45:43 +09:00

436 lines
12 KiB
C++

/*
* PROJECT: ReactOS Zip Shell Extension
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Create a zip file
* COPYRIGHT: Copyright 2019 Mark Jansen ([email protected])
* Copyright 2019-2026 Katayama Hirofumi MZ ([email protected])
*/
#include "precomp.h"
#include "atlsimpcoll.h"
#include "minizip/zip.h"
#include "minizip/iowin32.h"
#include <process.h>
static inline DWORD CalculateNameCRC32(PCSTR name)
{
return crc32(0L, reinterpret_cast<const BYTE*>(name), strlen(name));
}
static CStringW DoGetZipName(PCWSTR filename)
{
WCHAR szPath[MAX_PATH];
StringCbCopyW(szPath, sizeof(szPath), filename);
PathRemoveExtensionW(szPath);
CStringW ret = szPath;
ret += L".zip";
UINT i = 2;
while (PathFileExistsW(ret))
{
CStringW str;
str.Format(L" (%u).zip", i++);
ret = szPath;
ret += str;
}
return ret;
}
static CStringW DoGetBaseName(PCWSTR filename)
{
WCHAR szBaseName[MAX_PATH];
StringCbCopyW(szBaseName, sizeof(szBaseName), filename);
PathRemoveFileSpecW(szBaseName);
PathAddBackslashW(szBaseName);
return szBaseName;
}
static CStringA
DoGetNameInZip(const CStringW& basename, const CStringW& filename, UINT nCodePage)
{
CStringW basenameI = basename, filenameI = filename;
basenameI.MakeUpper();
filenameI.MakeUpper();
CStringW ret;
if (filenameI.Find(basenameI) == 0)
ret = filename.Mid(basename.GetLength());
else
ret = filename;
ret.Replace(L'\\', L'/');
return CStringA(CW2AEX<MAX_PATH>(ret, nCodePage));
}
static BOOL
DoGetFileTimeInfo(HANDLE hFile, zip_fileinfo* pzi)
{
FILETIME ft, ftLocal;
ZeroMemory(pzi, sizeof(*pzi));
if (GetFileTime(hFile, NULL, NULL, &ft))
{
SYSTEMTIME st;
FileTimeToLocalFileTime(&ft, &ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
pzi->tmz_date.tm_sec = st.wSecond;
pzi->tmz_date.tm_min = st.wMinute;
pzi->tmz_date.tm_hour = st.wHour;
pzi->tmz_date.tm_mday = st.wDay;
pzi->tmz_date.tm_mon = st.wMonth - 1;
pzi->tmz_date.tm_year = st.wYear;
return TRUE;
}
return FALSE;
}
static void
DoAddFilesFromItem(CSimpleArray<CStringW>& files, PCWSTR item)
{
if (!PathIsDirectoryW(item))
{
files.Add(item);
return;
}
WCHAR szPath[MAX_PATH];
StringCbCopyW(szPath, sizeof(szPath), item);
PathAppendW(szPath, L"*");
WIN32_FIND_DATAW find;
HANDLE hFind = FindFirstFileW(szPath, &find);
if (hFind == INVALID_HANDLE_VALUE)
return;
do
{
if (wcscmp(find.cFileName, L".") == 0 ||
wcscmp(find.cFileName, L"..") == 0)
{
continue;
}
StringCbCopyW(szPath, sizeof(szPath), item);
PathAppendW(szPath, find.cFileName);
if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
DoAddFilesFromItem(files, szPath);
else
files.Add(szPath);
} while (FindNextFileW(hFind, &find));
FindClose(hFind);
}
struct CZipCreatorImpl
{
CSimpleArray<CStringW> m_items;
CStringW m_ExistingZip;
CStringW m_TargetDir;
unsigned JustDoIt();
};
CZipCreator* CZipCreator::DoCreate(PCWSTR pszExistingZip, PCWSTR pszTargetDir)
{
CZipCreator* pCreator = new CZipCreator();
if (pszExistingZip)
pCreator->m_pimpl->m_ExistingZip = pszExistingZip;
if (pszTargetDir)
pCreator->m_pimpl->m_TargetDir = pszTargetDir;
return pCreator;
}
CZipCreator::CZipCreator() : m_pimpl(new CZipCreatorImpl)
{
InterlockedIncrement(&g_ModuleRefCnt);
}
CZipCreator::~CZipCreator()
{
InterlockedDecrement(&g_ModuleRefCnt);
delete m_pimpl;
}
static unsigned __stdcall
create_zip_function(void *arg)
{
CZipCreator *pCreator = reinterpret_cast<CZipCreator *>(arg);
unsigned result = pCreator->m_pimpl->JustDoIt();
if (result == 0 && pCreator->m_pidlNotify)
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, (LPITEMIDLIST)pCreator->m_pidlNotify, NULL);
return result;
}
BOOL CZipCreator::runThread(CZipCreator *pCreator)
{
unsigned tid = 0;
HANDLE hThread = reinterpret_cast<HANDLE>(
_beginthreadex(NULL, 0, create_zip_function, pCreator, 0, &tid));
if (hThread)
{
CloseHandle(hThread);
return TRUE;
}
DPRINT1("hThread == NULL\n");
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
CStringW strText(MAKEINTRESOURCEW(IDS_CANTSTARTTHREAD));
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
delete pCreator;
return FALSE;
}
void CZipCreator::DoAddItem(PCWSTR pszFile)
{
// canonicalize path
WCHAR szPath[MAX_PATH];
GetFullPathNameW(pszFile, _countof(szPath), szPath, NULL);
m_pimpl->m_items.Add(szPath);
}
enum CZC_ERROR
{
CZCERR_ZEROITEMS = 1,
CZCERR_NOFILES,
CZCERR_CREATE,
CZCERR_READ
};
unsigned CZipCreatorImpl::JustDoIt()
{
// TODO: Show progress.
CStringW strZipName;
INT appendMode;
if (m_ExistingZip.IsEmpty())
{
strZipName = DoGetZipName(m_items[0]);
appendMode = APPEND_STATUS_CREATE;
}
else
{
strZipName = m_ExistingZip;
appendMode = APPEND_STATUS_ADDINZIP;
}
if (m_items.GetSize() <= 0)
{
DPRINT1("GetSize() <= 0\n");
return CZCERR_ZEROITEMS;
}
CSimpleArray<CStringW> files;
for (INT iItem = 0; iItem < m_items.GetSize(); ++iItem)
{
DoAddFilesFromItem(files, m_items[iItem]);
}
if (files.GetSize() <= 0)
{
DPRINT1("files.GetSize() <= 0\n");
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
CStringW strText;
strText.Format(IDS_NOFILES, static_cast<PCWSTR>(m_items[0]));
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
return CZCERR_NOFILES;
}
zlib_filefunc64_def ffunc;
ZeroMemory(&ffunc, sizeof(ffunc));
fill_win32_filefunc64W(&ffunc);
zipFile zf = zipOpen2_64(strZipName, appendMode, NULL, &ffunc);
if (zf == 0)
{
DPRINT1("zf == 0\n");
int err = CZCERR_CREATE;
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
CStringW strText;
strText.Format(IDS_CANTCREATEZIP, static_cast<PCWSTR>(strZipName), err);
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
return err;
}
// TODO: password
const char *password = NULL;
// Use dwAllocationGranularity for file mapping
SYSTEM_INFO si;
GetSystemInfo(&si);
const ULONGLONG dwChunkSize = si.dwAllocationGranularity * 256;
// The loop for creating a ZIP file
CStringW strTarget, strBaseName = DoGetBaseName(m_items[0]);
UINT nCodePage = GetZipCodePage(FALSE);
INT err = ZIP_OK;
for (INT iFile = 0; iFile < files.GetSize() && err == ZIP_OK; ++iFile)
{
const CStringW& strFile = files[iFile];
zip_fileinfo zi;
// Open the file for storing it into a ZIP file
HANDLE hFile = CreateFileW(strFile, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DPRINT1("Cannot open file '%S'\n", (PCWSTR)strFile);
err = CZCERR_READ;
break;
}
// Load the file time info
DoGetFileTimeInfo(hFile, &zi);
// Get the file size
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(hFile, &fileSize))
{
DPRINT1("Cannot get file size '%S'\n", (PCWSTR)strFile);
CloseHandle(hFile);
err = CZCERR_READ;
break;
}
unsigned long crc = 0;
if (password)
{
// TODO: crc = ...;
}
// Get filename info
CStringA strNameInZip = DoGetNameInZip(strBaseName, strFile, nCodePage);
CStringA strNameInZipUTF8 = DoGetNameInZip(strBaseName, strFile, CP_UTF8);
if (!m_TargetDir.IsEmpty())
{
CStringA strTargetDir = CStringA(CW2AEX<MAX_PATH>(m_TargetDir, nCodePage));
strNameInZip = strTargetDir + strNameInZip;
CStringA strTargetDirUTF8 = CStringA(CW2AEX<MAX_PATH>(m_TargetDir, CP_UTF8));
strNameInZipUTF8 = strTargetDirUTF8 + strNameInZipUTF8;
}
// Prepare additional field (UTF-8 name)
CSimpleArray<BYTE> extraField;
if (nCodePage != CP_UTF8 && strNameInZip != strNameInZipUTF8)
{
// Header
WORD headerID = EF_UNIPATH, dataSize = 1 + 4 + strNameInZipUTF8.GetLength();
extraField.Add(headerID & 0xFF);
extraField.Add((headerID >> 8) & 0xFF);
extraField.Add(dataSize & 0xFF);
extraField.Add((dataSize >> 8) & 0xFF);
extraField.Add(1); // Version
// CRC32
DWORD nameCRC = CalculateNameCRC32(strNameInZip);
extraField.Add(nameCRC & 0xFF);
extraField.Add((nameCRC >> 8) & 0xFF);
extraField.Add((nameCRC >> 16) & 0xFF);
extraField.Add((nameCRC >> 24) & 0xFF);
// UTF-8 name
for (INT ich = 0; ich < strNameInZipUTF8.GetLength(); ++ich)
extraField.Add(strNameInZipUTF8[ich]);
}
// Add a new file entry into a ZIP file
err = zipOpenNewFileInZip4_64(zf, strNameInZip, &zi,
(extraField.GetSize() ? extraField.GetData() : NULL),
extraField.GetSize(), NULL, 0, NULL, Z_DEFLATED,
Z_DEFAULT_COMPRESSION, 0, -MAX_WBITS, DEF_MEM_LEVEL,
Z_DEFAULT_STRATEGY, password, crc,
MINIZIP_COMPATIBLE_VERSION,
(nCodePage == CP_UTF8 ? MINIZIP_UTF8_FLAG : 0), 1);
if (err)
{
DPRINT1("zipOpenNewFileInZip3_64\n");
break;
}
if (fileSize.QuadPart > 0) // Not an empty file?
{
// Use a file mapping for quick file access and large file support
HANDLE hMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hMapping)
{
DPRINT1("Cannot create file mapping\n");
err = CZCERR_READ;
CloseHandle(hFile);
break;
}
// The loop for reading and storing file contents
DWORD dwMapSize;
for (ULONGLONG offset = 0; offset < (ULONGLONG)fileSize.QuadPart && err == ZIP_OK;
offset += dwMapSize)
{
ULONGLONG dwDelta = fileSize.QuadPart - offset;
dwMapSize = (DWORD)min(dwChunkSize, dwDelta);
DWORD offsetLow = (DWORD)offset, offsetHigh = (DWORD)(offset >> 32);
PVOID pData = MapViewOfFile(hMapping, FILE_MAP_READ, offsetHigh, offsetLow,
dwMapSize);
if (!pData)
{
DPRINT1("Cannot map the view\n");
err = CZCERR_READ;
break;
}
err = zipWriteInFileInZip(zf, pData, dwMapSize);
UnmapViewOfFile(pData);
}
CloseHandle(hMapping);
}
CloseHandle(hFile);
zipCloseFileInZip(zf);
}
zipClose(zf, NULL);
if (err)
{
if (err && m_ExistingZip.IsEmpty())
DeleteFileW(strZipName);
CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
CStringW strText;
if (err < 0)
strText.Format(IDS_CANTCREATEZIP, static_cast<PCWSTR>(strZipName), err);
else
strText.Format(IDS_CANTREADFILE, static_cast<PCWSTR>(strTarget));
MessageBoxW(NULL, strText, strTitle, MB_ICONERROR);
}
else
{
WCHAR szFullPath[MAX_PATH];
GetFullPathNameW(strZipName, _countof(szFullPath), szFullPath, NULL);
if (m_ExistingZip.IsEmpty())
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, szFullPath, NULL);
else
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, szFullPath, NULL);
}
return err;
}