VOID WINAPI HashVerifySortColumn( PHASHVERIFYCONTEXT phvctx, LPNMLISTVIEW plv ) { if (phvctx->status != CLEANUP_COMPLETED) return; // Sorting is available only after the worker is done // Capture the current selection/focus state HashVerifyReadStates(phvctx); if (phvctx->sort.iColumn != plv->iSubItem) { // Change to a new column phvctx->sort.iColumn = plv->iSubItem; phvctx->sort.bReverse = FALSE; qsort_s_uptr(phvctx->index, phvctx->cTotal, sizeof(PHVITEM), HashVerifySortCompare, phvctx); } else if (phvctx->sort.bReverse) { // Clicking a column thrice in a row reverts to the original file order phvctx->sort.iColumn = -1; phvctx->sort.bReverse = FALSE; // We do need to validate phvctx->index to handle the edge case where // the list is really non-empty, but we are treating it as empty because // we could not allocate an index (qsort_s uses the given length while // SLBuildIndex uses the actual length); this is, admittedly, a very // extreme edge case, as it crops up only in an OOM situation where the // user tries to click-sort an empty list view! if (phvctx->index) SLBuildIndex(phvctx->hList, phvctx->index); } else { // Clicking a column twice in a row reverses the order; since we are // just reversing the order of an already-sorted column, we can just // naively flip the index if (phvctx->index) { PHVITEM pItemTemp; PPHVITEM ppItemLow = phvctx->index; PPHVITEM ppItemHigh = phvctx->index + phvctx->cTotal - 1; while (ppItemHigh > ppItemLow) { pItemTemp = *ppItemLow; *ppItemLow = *ppItemHigh; *ppItemHigh = pItemTemp; ++ppItemLow; --ppItemHigh; } } phvctx->sort.bReverse = TRUE; } // Restore the selection/focus state HashVerifySetStates(phvctx); // Update the UI { HWND hWndHeader = ListView_GetHeader(phvctx->hWndList); INT i; HDITEM hdi; hdi.mask = HDI_FORMAT; for (i = HV_COL_FIRST; i <= HV_COL_LAST; ++i) { Header_GetItem(hWndHeader, i, &hdi); hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); if (phvctx->sort.iColumn == i) hdi.fmt |= (phvctx->sort.bReverse) ? HDF_SORTDOWN : HDF_SORTUP; Header_SetItem(hWndHeader, i, &hdi); } // Invalidate all items ListView_RedrawItems(phvctx->hWndList, 0, phvctx->cTotal); // Set a light gray background on the sorted column ListView_SetSelectedColumn( phvctx->hWndList, (phvctx->sort.iColumn != HV_COL_STATUS) ? phvctx->sort.iColumn : -1 ); // Unfortunately, the list does not automatically repaint all of the // areas affected by SetSelectedColumn, so it is necessary to force a // repaint of the list view's visible areas in order to avoid artifacts InvalidateRect(phvctx->hWndList, NULL, FALSE); } }
VOID WINAPI HashVerifyParseData( PHASHVERIFYCONTEXT phvctx ) { PTSTR pszData = phvctx->pszFileData; // Points to the next line to process UINT cchChecksum; // Expected length of the checksum in TCHARs BOOL bReverseFormat = FALSE; // TRUE if using SFV's format of putting the checksum last BOOL bLinesRemaining = TRUE; // TRUE if we have not reached the end of the data // Try to determine the file type from the extension { PTSTR pszExt = StrRChr(phvctx->pszPath, NULL, TEXT('.')); if (pszExt) { if (StrCmpI(pszExt, TEXT(".sfv")) == 0) { phvctx->whctx.flags = WHEX_CHECKCRC32; cchChecksum = 8; bReverseFormat = TRUE; } else if (StrCmpI(pszExt, TEXT(".md4")) == 0) { phvctx->whctx.flags = WHEX_CHECKMD4; cchChecksum = 32; } else if (StrCmpI(pszExt, TEXT(".md5")) == 0) { phvctx->whctx.flags = WHEX_CHECKMD5; cchChecksum = 32; } else if (StrCmpI(pszExt - 1, TEXT(".sha1")) == 0) { phvctx->whctx.flags = WHEX_CHECKSHA1; cchChecksum = 40; } } } while (bLinesRemaining) { PTSTR pszStartOfLine; // First non-whitespace character of the line PTSTR pszEndOfLine; // Last non-whitespace character of the line PTSTR pszChecksum = NULL, pszFileName = NULL; INT16 cchPath; // This INCLUDES the NULL terminator! // Step 1: Isolate the current line as a NULL-terminated string { pszStartOfLine = pszData; // Find the end of the line while (*pszData && *pszData != TEXT('\n')) ++pszData; // Terminate it if necessary, otherwise flag the end of the data if (*pszData) *pszData = 0; else bLinesRemaining = FALSE; pszEndOfLine = pszData; // Strip spaces from the end of the line... while (--pszEndOfLine >= pszStartOfLine && *pszEndOfLine == TEXT(' ')) *pszEndOfLine = 0; // ...and from the start of the line while (*pszStartOfLine == TEXT(' ')) ++pszStartOfLine; // Skip past this line's terminator; point at the remaining data ++pszData; } // Step 2a: Parse the line as SFV if (bReverseFormat) { pszEndOfLine -= 7; if (pszEndOfLine > pszStartOfLine && ValidateHexSequence(pszEndOfLine, 8)) { pszChecksum = pszEndOfLine; // Trim spaces between the checksum and the file name while (--pszEndOfLine >= pszStartOfLine && *pszEndOfLine == TEXT(' ')) *pszEndOfLine = 0; // Lines that begin with ';' are comments in SFV if (*pszStartOfLine && *pszStartOfLine != TEXT(';')) pszFileName = pszStartOfLine; } } // Step 2b: All other file formats else { // If we do not know the type yet, make a stab at detecting it if (phvctx->whctx.flags == 0) { if (ValidateHexSequence(pszStartOfLine, 8)) { cchChecksum = 8; phvctx->whctx.flags = WHEX_ALL32; // WHEX_CHECKCRC32 } else if (ValidateHexSequence(pszStartOfLine, 32)) { cchChecksum = 32; phvctx->whctx.flags = WHEX_ALL128; // WHEX_CHECKMD4 | WHEX_CHECKMD5 // Disambiguate from the filename, if possible if (StrStrI(phvctx->pszPath, TEXT("MD5"))) phvctx->whctx.flags = WHEX_CHECKMD5; else if (StrStrI(phvctx->pszPath, TEXT("MD4"))) phvctx->whctx.flags = WHEX_CHECKMD4; } else if (ValidateHexSequence(pszStartOfLine, 40)) { cchChecksum = 40; phvctx->whctx.flags = WHEX_ALL160; // WHEX_CHECKSHA1 } } // Parse the line if ( phvctx->whctx.flags && pszEndOfLine > pszStartOfLine + cchChecksum && ValidateHexSequence(pszStartOfLine, cchChecksum) ) { pszChecksum = pszStartOfLine; pszStartOfLine += cchChecksum + 1; // Skip over spaces between the checksum and filename while (*pszStartOfLine == TEXT(' ')) ++pszStartOfLine; if (*pszStartOfLine) pszFileName = pszStartOfLine; } } // Step 3: Do something useful with the results if (pszFileName && (cchPath = (INT16)(pszEndOfLine + 2 - pszFileName)) > 1) { // Since pszEndOfLine points to the character BEFORE the terminator, // cchLine == 1 + pszEnd - pszStart, and then +1 for the NULL // terminator means that we need to add 2 TCHARs to the length // By treating cchPath as INT16 and checking the sign, we ensure // that the path does not exceed 32K. // Create the new data block PHASHVERIFYITEM pItem = SLAddItem(phvctx->hList, NULL, sizeof(HASHVERIFYITEM)); // Abort if we are out of memory if (!pItem) break; pItem->filesize.ui64 = -1; pItem->filesize.sz[0] = 0; pItem->pszDisplayName = pszFileName; pItem->pszExpected = pszChecksum; pItem->cchDisplayName = cchPath; pItem->uStatusID = HV_STATUS_NULL; pItem->szActual[0] = 0; ++phvctx->cTotal; } // If the current line was found to be valid } // Loop until there are no lines left // Build the index if ( phvctx->cTotal && (phvctx->index = SLSetContextSize(phvctx->hList, phvctx->cTotal * sizeof(PHVITEM))) ) { SLBuildIndex(phvctx->hList, phvctx->index); } else { phvctx->cTotal = 0; } }
VOID __fastcall HashSaveWorkerMain( PHASHSAVECONTEXT phsctx ) { // Note that ALL message communication to and from the main window MUST // be asynchronous, or else there may be a deadlock. // Prep: expand directories, max path, etc. (prefix was set by earlier call) PostMessage(phsctx->hWnd, HM_WORKERTHREAD_TOGGLEPREP, (WPARAM)phsctx, TRUE); if (! HashCalcPrepare(phsctx)) return; HashCalcSetSaveFormat(phsctx); PostMessage(phsctx->hWnd, HM_WORKERTHREAD_TOGGLEPREP, (WPARAM)phsctx, FALSE); // Extract the slist into a vector for parallel_for_each std::vector<PHASHSAVEITEM> vecpItems; vecpItems.resize(phsctx->cTotal + 1); SLBuildIndex(phsctx->hList, (PVOID*)vecpItems.data()); assert(vecpItems.back() == nullptr); vecpItems.pop_back(); assert(vecpItems.back() != nullptr); #ifdef USE_PPL const bool bMultithreaded = vecpItems.size() > 1 && IsSSD(vecpItems[0]->szPath); concurrency::concurrent_vector<void*> vecBuffers; // a vector of all allocated read buffers (one per thread) DWORD dwBufferTlsIndex = TlsAlloc(); // TLS index of the current thread's read buffer if (dwBufferTlsIndex == TLS_OUT_OF_INDEXES) return; #else constexpr bool bMultithreaded = false; #endif PBYTE pbTheBuffer; // file read buffer, used iff not multithreaded if (! bMultithreaded) { pbTheBuffer = (PBYTE)VirtualAlloc(NULL, READ_BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE); if (pbTheBuffer == NULL) return; } // Initialize the progress bar update synchronization vars CRITICAL_SECTION updateCritSec; volatile ULONGLONG cbCurrentMaxSize = 0; if (bMultithreaded) InitializeCriticalSection(&updateCritSec); #ifdef _TIMED DWORD dwStarted; dwStarted = GetTickCount(); #endif class CanceledException {}; // concurrency::parallel_for_each(vecpItems.cbegin(), vecpItems.cend(), ... auto per_file_worker = [&](PHASHSAVEITEM pItem) { WHCTXEX whctx; // Indicate which hash type we are after, see WHEX... values in WinHash.h whctx.dwFlags = 1 << (phsctx->ofn.nFilterIndex - 1); PBYTE pbBuffer; #ifdef USE_PPL if (bMultithreaded) { // Allocate or retrieve the already-allocated read buffer for the current thread pbBuffer = (PBYTE)TlsGetValue(dwBufferTlsIndex); if (pbBuffer == NULL) { pbBuffer = (PBYTE)VirtualAlloc(NULL, READ_BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE); if (pbBuffer == NULL) throw CanceledException(); // Cache the read buffer for the current thread vecBuffers.push_back(pbBuffer); TlsSetValue(dwBufferTlsIndex, pbBuffer); } } #endif #pragma warning(push) #pragma warning(disable: 4700 4703) // potentially uninitialized local pointer variable 'pbBuffer' used // Get the hash WorkerThreadHashFile( (PCOMMONCONTEXT)phsctx, pItem->szPath, &whctx, &pItem->results, bMultithreaded ? pbBuffer : pbTheBuffer, NULL, 0, bMultithreaded ? &updateCritSec : NULL, &cbCurrentMaxSize #ifdef _TIMED , &pItem->dwElapsed #endif ); if (phsctx->status == PAUSED) WaitForSingleObject(phsctx->hUnpauseEvent, INFINITE); if (phsctx->status == CANCEL_REQUESTED) throw CanceledException(); // Write the data HashCalcWriteResult(phsctx, pItem); // Update the UI InterlockedIncrement(&phsctx->cSentMsgs); PostMessage(phsctx->hWnd, HM_WORKERTHREAD_UPDATE, (WPARAM)phsctx, (LPARAM)pItem); }; #pragma warning(pop) try { #ifdef USE_PPL if (bMultithreaded) concurrency::parallel_for_each(vecpItems.cbegin(), vecpItems.cend(), per_file_worker); else #endif std::for_each(vecpItems.cbegin(), vecpItems.cend(), per_file_worker); } catch (CanceledException) {} // ignore cancellation requests #ifdef _TIMED if (phsctx->cTotal > 1 && phsctx->status != CANCEL_REQUESTED) { union { CHAR szA[MAX_STRINGMSG]; WCHAR szW[MAX_STRINGMSG]; } buffer; size_t cbBufferLeft; if (phsctx->opt.dwSaveEncoding == 1) // UTF-16 { StringCbPrintfExW(buffer.szW, sizeof(buffer), NULL, &cbBufferLeft, 0, L"; Total elapsed: %d ms\r\n", GetTickCount() - dwStarted); } else // UTF-8 or ANSI { StringCbPrintfExA(buffer.szA, sizeof(buffer), NULL, &cbBufferLeft, 0, "; Total elapsed: %d ms\r\n", GetTickCount() - dwStarted); } DWORD dwUnused; WriteFile(phsctx->hFileOut, buffer.szA, (DWORD) (sizeof(buffer) - cbBufferLeft), &dwUnused, NULL); } #endif #ifdef USE_PPL if (bMultithreaded) { for (void* pBuffer : vecBuffers) VirtualFree(pBuffer, 0, MEM_RELEASE); DeleteCriticalSection(&updateCritSec); } else #endif VirtualFree(pbTheBuffer, 0, MEM_RELEASE); }