diff options
Diffstat (limited to 'win32/droplet/replace.cpp')
-rw-r--r-- | win32/droplet/replace.cpp | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/win32/droplet/replace.cpp b/win32/droplet/replace.cpp new file mode 100644 index 0000000..8a7b51b --- /dev/null +++ b/win32/droplet/replace.cpp @@ -0,0 +1,438 @@ +/* + * AUTHOR + * N. Nielsen + * + * LICENSE + * This software is in the public domain. + * + * The software is provided "as is", without warranty of any kind, + * express or implied, including but not limited to the warranties + * of merchantability, fitness for a particular purpose, and + * noninfringement. In no event shall the author(s) be liable for any + * claim, damages, or other liability, whether in an action of + * contract, tort, or otherwise, arising from, out of, or in connection + * with the software or the use or other dealings in the software. + * + * SUPPORT + * Send bug reports to: <nielsen@memberwebs.com> + */ + +#include "stdafx.h" +#include "Replace.h" +#include "common/errutil.h" +#include "common/rliberr.h" +#include "common/mystring.h" + + +// Error Macros: ------------------------------------------------- +// Display error messages and all that +// Only valid in this context +// Each of these jumps to the 'cleanup' label for wrapping +// up any open objects. + +#define RETURN(v) \ + { ret = (v); goto cleanup; } +#define WINERR(f) \ + { HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); RETURN(errorMessage(m_dlg.m_hWnd, hr, f)); } +#define WINERR_1(f, a) \ + { HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); RETURN(errorMessage(m_dlg.m_hWnd, hr, f, a)); } +#define WINERR_E(e, f) \ + { RETURN(errorMessage(m_dlg.m_hWnd, e, f)); } +#define REPERR(c, s) \ + { RETURN(rlibError(m_dlg.m_hWnd, c, s)); } + + +// The extension appended to backup or temp files +LPCTSTR kBackupExt = _T(".x_r"); + +// (Con|De)struction: ------------------------------------------- + +Replace::Replace() +{ + m_outFile = NULL; +} + +Replace::~Replace() +{ + ASSERT(m_outFile == NULL); +} + + +// initialize: --------------------------------------------------- +// Loads the script from resources and sets up dialog +// in another thread + +HRESULT Replace::initialize() +{ + HRESULT ret = S_OK; + + { + if(!m_droplet.load(GetModuleHandle(NULL))) + { + errorMessage(NULL, 0, _T("Couldn't access droplet rep script. The droplet is probably corrupted.")); + RETURN(E_FAIL); + } + + m_dlg.setTitle(m_droplet.getTitle()); + + r_context& ctx = m_droplet.getContext(); + + int r = repInit(&ctx); + if(r < 0) + REPERR(r, &(ctx.script)); + + // This starts the dialog in another thread + // in order to keep it responsive + m_dlg.startDialog(); + } + +cleanup: + return ret; +} + + +// terminate: ---------------------------------------------------- +// Undoes above + +HRESULT Replace::terminate() +{ + m_dlg.stopDialog(); + r_context& ctx = m_droplet.getContext(); + repFree(&ctx); + return S_OK; +} + + +// writeFile: ---------------------------------------------------- +// The callback called from within rlib when data output +// is required. The argument passed through the r_stream +// is a pointer to the Replace object. + +int Replace::writeFile(r_stream* stream, byte* data, size_t len) +{ + Replace* replace = (Replace*)stream->arg; + + // The m_outFile member should be set (and only set) + // whenever we're inside rlibRun + ASSERT(replace->m_outFile != NULL); + DWORD written = 0; + + // Double check that everything was written + return WriteFile(replace->m_outFile, data, len, &written, NULL) && len == written; +} + +// matchStatus: -------------------------------------------------- +// Callback whenever something is replaced in rlib +// Updates the dialog. + +int Replace::matchStatus(r_stream* stream, r_replace* repl) +{ + Replace* replace = (Replace*)stream->arg; + ASSERT_PTR(replace); + + replace->m_dlg.onReplaced(); + return replace->m_dlg.isCancelled() ? 1 : 0; +} + + +// replaceFolder: ------------------------------------------------ +// Recursively processes a folder using replaceSingleFile + +HRESULT Replace::replaceFolder(LPCTSTR folder) +{ + HANDLE hFindFile = NULL; + WIN32_FIND_DATA findData; + TCHAR old[260]; // Save the current folder (as we'll change it) + HRESULT ret = S_FALSE; + + { + // Backup the current directory + if(!GetCurrentDirectory(260, old)) + WINERR("Couldn't get the current directory."); + + if(!SetCurrentDirectory(folder)) + WINERR_1("Couldn't change the current directory to %s", folder); + + if(hFindFile = FindFirstFile(_T("*.*"), &findData)) + { + // And for every file + do + { + HRESULT r = S_FALSE; + if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if(findData.cFileName[0] == '.' && + (findData.cFileName[1] == '.' || findData.cFileName[1] == '\0')) + continue; + + r = replaceFolder(findData.cFileName); + } + else + { + r = replaceSingleFile(findData.cFileName); + } + + if(FAILED(r)) + RETURN(r); + if(r == S_OK) + ret = r; + } + while(FindNextFile(hFindFile, &findData)); + } + + // Make sure we didn't miss anything + if(::GetLastError() != 0 && ::GetLastError() != ERROR_NO_MORE_FILES) + WINERR_1("Couldn't list the directory %s", folder); + + // Change directory back + SetCurrentDirectory(old); + } + +cleanup: + if(hFindFile) + FindClose(hFindFile); + + return ret; +} + + +// replaceSingleFile: ---------------------------------------------- +// Process a file into a temp file or backup etc... +// Calls replaceFilename for actual processing + +HRESULT Replace::replaceSingleFile(LPCTSTR fileName) +{ + HRESULT ret = S_FALSE; + string temp; + USES_CONVERSION; + + r_context& ctx = m_droplet.getContext(); + + { + // Update the dialog display + m_dlg.onNewFile(fileName); + + // This lets us use the filename from within the script + ret = rlibSetVar(&(ctx.stream), "FILENAME", T2CA(fileName)); + if(ret < 0) + REPERR(ret, &(ctx.script)); + + // Make a temp/backup name + temp.assign(fileName); + temp.append(kBackupExt); + + // Now we do this 2 ways, if we're keeping backups then... + if(m_droplet.keepBackups()) + { + DeleteFile(temp); + + // Rename the original file to the backup + if(!MoveFile(fileName, temp)) + WINERR_1("There was an error while creating the backup file: %s", temp.c_str()); + + // Do a replacement operation back to the original + // from the backup + ret = replaceFilename(temp, fileName); + + // If there were no replacements + if(ret != S_OK) + { + DeleteFile(fileName); + + // And rename the backup file back + if(!MoveFile(temp, fileName)) + WINERR_1("There was an error while renaming the file: %s", fileName); + } + } + + // No backups, much faster (when there's no replacements)! + else + { + // Do a replacement operation to a temp file + ret = replaceFilename(fileName, temp); + + // If there were replacements + if(ret == S_OK) + { + DeleteFile(fileName); + + // Rename temp to original + if(!MoveFile(temp, fileName)) + WINERR_1("There was an error while renaming the file: %s", fileName); + } + + // If no replacements + else + { + // Remove temp file + DeleteFile(temp); + } + } + } + +cleanup: + + return ret; +} + +// replaceFilename: ------------------------------------------------- +// Process one file into another +// Open the files and passes them to replaceFile + +HRESULT Replace::replaceFilename(LPCTSTR fileIn, LPCTSTR fileOut) +{ + HANDLE in = INVALID_HANDLE_VALUE; + HANDLE out = INVALID_HANDLE_VALUE; + HRESULT ret = S_FALSE; + + { + // Basic readonly open + in = CreateFile(fileIn, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, 0, NULL); + if(in == INVALID_HANDLE_VALUE) + WINERR_1("There was an error opening the file: %s", fileIn); + + // We need to know whether we're NT or not below + OSVERSIONINFO os; + os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&os); + + // This creates a new file for writing + // If we're running on NT, then use exactly the same + // attributes and permissions as original file + out = CreateFile(fileOut, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, + os.dwPlatformId & VER_PLATFORM_WIN32_NT ? in : NULL); + if(out == INVALID_HANDLE_VALUE) + WINERR_1("There was an error opening the file: %s", fileIn); + + + // Do replacements! + ret = replaceFile(in, out); + + } + +cleanup: + + if(in != INVALID_HANDLE_VALUE) + CloseHandle(in); + if(out != INVALID_HANDLE_VALUE) + CloseHandle(out); + + return ret; +} + + +// replaceFile: -------------------------------------------------------- +// Process one open file handle into another +// This memory maps the input file and passes it to replaceBuffer + +HRESULT Replace::replaceFile(HANDLE in, HANDLE out) +{ + HANDLE mapping = NULL; + void* data = NULL; + HRESULT ret = S_FALSE; + + { + // Memory map the input file + mapping = CreateFileMapping(in, NULL, PAGE_READONLY, 0, 0, NULL); + if(!mapping) + WINERR("There was an error reading the input file."); + + data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + if(!data) + WINERR("There was an error reading the input file."); + + size_t size = GetFileSize(in, NULL); + if(size == INVALID_FILE_SIZE) + WINERR("There was an error reading the input file."); + + // And pass it on + ret = replaceBuffer((byte*)data, size, out); + } + +cleanup: + + if(data) + UnmapViewOfFile(data); + if(mapping) + CloseHandle(mapping); + + return ret; +} + + +// replaceBuffer: ---------------------------------------------------- +// Process a memory buffer into a file for output +// Uses the rlib functions directly + +HRESULT Replace::replaceBuffer(byte* data, size_t size, HANDLE out) +{ + ASSERT(m_outFile == NULL); + + size_t blockSize = m_droplet.getBuffSize() * 2; // Block size + size_t batchSize; // Current batch size + int r = R_IN; + bool dirty = false; + HRESULT ret = S_OK; + + { + // If there's no blocksize then we process the + // entire file at once + if(blockSize == 0) + blockSize = size; + + batchSize = blockSize; + + // Setup the rlib stream + r_context& ctx = m_droplet.getContext(); + + ctx.stream.nextIn = data; + ctx.stream.availIn = blockSize; + ctx.stream.fWrite = writeFile; + ctx.stream.arg = this; + ctx.stream.fMatch = matchStatus; + + // This is used by writeFile + m_outFile = out; + + // While we have more data to put in ... + while(size > 0 || r == R_IN) + { + // If rlib wants data then give it + if(r == R_IN) + { + batchSize = (blockSize > size) ? size : blockSize; + + ctx.stream.nextIn = data; + ctx.stream.availIn = batchSize; + + } + + // call rlib + r = rlibRun(&(ctx.stream), &(ctx.script), (size - batchSize) <= 0); + + // Oops! + if(r < 0) + REPERR(r, &(ctx.script)); + + if(ctx.stream.total > 0) + dirty = true; + + // Increment last batch + data += (batchSize - ctx.stream.availIn); + size -= (batchSize - ctx.stream.availIn); + } + + // Clears and prepares for next file + rlibClear(&ctx.stream); + } + +cleanup: + + m_outFile = NULL; + + if(FAILED(ret)) + return ret; + + return dirty ? S_OK : S_FALSE; +} |