mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-22 20:29:51 +00:00
432b1c4cc6
Was setting the original pathname improperly, so when attempting to add a file to a NuFX archive the "do you want to overwrite existing entry?" dialog contained gibberish.
2492 lines
79 KiB
C++
2492 lines
79 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* Bridge between NufxLib and GenericArchive.
|
|
*/
|
|
#include "stdafx.h"
|
|
#include "NufxArchive.h"
|
|
#include "ConfirmOverwriteDialog.h"
|
|
#include "RenameEntryDialog.h"
|
|
#include "RecompressOptionsDialog.h"
|
|
#include "AddClashDialog.h"
|
|
#include "Main.h"
|
|
#include "../nufxlib/NufxLib.h"
|
|
|
|
/*
|
|
* NufxLib doesn't currently allow an fssep of '\0', so we use this instead
|
|
* to indicate the absence of an fssep char. Not quite right, but it'll do
|
|
* until NufxLib gets fixed.
|
|
*/
|
|
const unsigned char kNufxNoFssep = 0xff;
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxEntry
|
|
* ===========================================================================
|
|
*/
|
|
|
|
int NufxEntry::ExtractThreadToBuffer(int which, char** ppText, long* pLength,
|
|
CString* pErrMsg) const
|
|
{
|
|
NuError nerr;
|
|
char* dataBuf = NULL;
|
|
NuDataSink* pDataSink = NULL;
|
|
NuThread thread;
|
|
unsigned long actualThreadEOF;
|
|
NuThreadIdx threadIdx;
|
|
bool needAlloc = true;
|
|
int result = -1;
|
|
|
|
ASSERT(IDOK != -1 && IDCANCEL != -1); // make sure return vals don't clash
|
|
|
|
if (*ppText != NULL)
|
|
needAlloc = false;
|
|
|
|
FindThreadInfo(which, &thread, pErrMsg);
|
|
if (!pErrMsg->IsEmpty())
|
|
goto bail;
|
|
threadIdx = thread.threadIdx;
|
|
actualThreadEOF = thread.actualThreadEOF;
|
|
|
|
/*
|
|
* We've got the right thread. Create an appropriately-sized buffer
|
|
* and extract the data into it (WITHOUT doing EOL conversion).
|
|
*
|
|
* First check for a length of zero.
|
|
*/
|
|
if (actualThreadEOF == 0) {
|
|
LOGI("Empty thread");
|
|
if (needAlloc) {
|
|
*ppText = new char[1];
|
|
**ppText = '\0';
|
|
}
|
|
*pLength = 0;
|
|
result = IDOK;
|
|
goto bail;
|
|
}
|
|
|
|
if (needAlloc) {
|
|
dataBuf = new char[actualThreadEOF];
|
|
if (dataBuf == NULL) {
|
|
pErrMsg->Format(L"allocation of %ld bytes failed",
|
|
actualThreadEOF);
|
|
goto bail;
|
|
}
|
|
} else {
|
|
if (*pLength < (long) actualThreadEOF) {
|
|
pErrMsg->Format(L"buf size %ld too short (%ld)",
|
|
*pLength, actualThreadEOF);
|
|
goto bail;
|
|
}
|
|
dataBuf = *ppText;
|
|
}
|
|
nerr = NuCreateDataSinkForBuffer(true, kNuConvertOff,
|
|
(unsigned char*)dataBuf, actualThreadEOF, &pDataSink);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"unable to create buffer data sink: %hs",
|
|
NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
SET_PROGRESS_BEGIN();
|
|
nerr = NuExtractThread(fpArchive, threadIdx, pDataSink);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr == kNuErrAborted) {
|
|
result = IDCANCEL;
|
|
//::sprintf(errorBuf, "Cancelled.\n");
|
|
} else if (nerr == kNuErrBadFormat) {
|
|
pErrMsg->Format(L"The compression method used on this file is not supported "
|
|
L"by your copy of \"nufxlib.dll\". For more information, "
|
|
L"please visit us on the web at "
|
|
L"http://www.faddensoft.com/ciderpress/");
|
|
} else {
|
|
pErrMsg->Format(L"unable to extract thread %ld: %hs",
|
|
threadIdx, NuStrError(nerr));
|
|
}
|
|
goto bail;
|
|
}
|
|
|
|
if (needAlloc)
|
|
*ppText = dataBuf;
|
|
*pLength = actualThreadEOF;
|
|
result = IDOK;
|
|
|
|
bail:
|
|
if (result == IDOK) {
|
|
SET_PROGRESS_END();
|
|
ASSERT(pErrMsg->IsEmpty());
|
|
} else {
|
|
ASSERT(result == IDCANCEL || !pErrMsg->IsEmpty());
|
|
if (needAlloc) {
|
|
delete[] dataBuf;
|
|
ASSERT(*ppText == NULL);
|
|
}
|
|
}
|
|
if (pDataSink != NULL)
|
|
NuFreeDataSink(pDataSink);
|
|
return result;
|
|
}
|
|
|
|
int NufxEntry::ExtractThreadToFile(int which, FILE* outfp, ConvertEOL conv,
|
|
ConvertHighASCII convHA, CString* pErrMsg) const
|
|
{
|
|
NuDataSink* pDataSink = NULL;
|
|
NuError nerr;
|
|
NuThread thread;
|
|
unsigned long actualThreadEOF;
|
|
NuThreadIdx threadIdx;
|
|
int result = -1;
|
|
|
|
ASSERT(outfp != NULL);
|
|
|
|
//CString errMsg;
|
|
FindThreadInfo(which, &thread, pErrMsg);
|
|
if (!pErrMsg->IsEmpty())
|
|
goto bail;
|
|
threadIdx = thread.threadIdx;
|
|
actualThreadEOF = thread.actualThreadEOF;
|
|
|
|
/* we've got the right thread, see if it's empty */
|
|
if (actualThreadEOF == 0) {
|
|
LOGI("Empty thread");
|
|
result = IDOK;
|
|
goto bail;
|
|
}
|
|
|
|
/* set EOL conversion flags */
|
|
NuValue nuConv;
|
|
switch (conv) {
|
|
case kConvertEOLOff: nuConv = kNuConvertOff; break;
|
|
case kConvertEOLOn: nuConv = kNuConvertOn; break;
|
|
case kConvertEOLAuto: nuConv = kNuConvertAuto; break;
|
|
default:
|
|
ASSERT(false);
|
|
pErrMsg->Format(L"internal error: bad conv flag %d", conv);
|
|
goto bail;
|
|
}
|
|
if (which == kDiskImageThread) {
|
|
/* override the above; never EOL-convert a disk image */
|
|
nuConv = kNuConvertOff;
|
|
}
|
|
|
|
switch (convHA) {
|
|
case kConvertHAOff:
|
|
nerr = NuSetValue(fpArchive, kNuValueStripHighASCII, false);
|
|
break;
|
|
case kConvertHAOn:
|
|
case kConvertHAAuto:
|
|
nerr = NuSetValue(fpArchive, kNuValueStripHighASCII, true);
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
pErrMsg->Format(L"internal error: bad convHA flag %d", convHA);
|
|
goto bail;
|
|
}
|
|
|
|
/* make sure we convert to CRLF */
|
|
nerr = NuSetValue(fpArchive, kNuValueEOL, kNuEOLCRLF); // for Win32
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"failed setting EOL value: %hs", NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/* create a data sink for "outfp" */
|
|
nerr = NuCreateDataSinkForFP(true, nuConv, outfp, &pDataSink);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"unable to create FP data sink: %hs",
|
|
NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/* extract the thread to the file */
|
|
SET_PROGRESS_BEGIN();
|
|
nerr = NuExtractThread(fpArchive, threadIdx, pDataSink);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr == kNuErrAborted) {
|
|
/* user hit the "cancel" button */
|
|
*pErrMsg = L"cancelled";
|
|
result = IDCANCEL;
|
|
} else if (nerr == kNuErrBadFormat) {
|
|
pErrMsg->Format(L"The compression method used on this file is not supported "
|
|
L"by your copy of \"nufxlib.dll\". For more information, "
|
|
L"please visit us on the web at "
|
|
L"http://www.faddensoft.com/ciderpress/");
|
|
} else {
|
|
pErrMsg->Format(L"unable to extract thread %ld: %hs",
|
|
threadIdx, NuStrError(nerr));
|
|
}
|
|
goto bail;
|
|
}
|
|
|
|
result = IDOK;
|
|
|
|
bail:
|
|
if (result == IDOK) {
|
|
SET_PROGRESS_END();
|
|
}
|
|
if (pDataSink != NULL)
|
|
NuFreeDataSink(pDataSink);
|
|
return result;
|
|
}
|
|
|
|
void NufxEntry::FindThreadInfo(int which, NuThread* pRetThread,
|
|
CString* pErrMsg) const
|
|
{
|
|
NuError nerr;
|
|
|
|
ASSERT(pErrMsg->IsEmpty());
|
|
|
|
/*
|
|
* Retrieve the record from the archive.
|
|
*/
|
|
const NuRecord* pRecord;
|
|
nerr = NuGetRecord(fpArchive, fRecordIdx, &pRecord);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"NufxLib unable to locate record %ld: %hs",
|
|
fRecordIdx, NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Find the right thread.
|
|
*/
|
|
const NuThread* pThread;
|
|
unsigned long wantedThreadID;
|
|
switch (which) {
|
|
case kDataThread: wantedThreadID = kNuThreadIDDataFork; break;
|
|
case kRsrcThread: wantedThreadID = kNuThreadIDRsrcFork; break;
|
|
case kDiskImageThread: wantedThreadID = kNuThreadIDDiskImage; break;
|
|
case kCommentThread: wantedThreadID = kNuThreadIDComment; break;
|
|
default:
|
|
pErrMsg->Format(L"looking for bogus thread 0x%02x", which);
|
|
goto bail;
|
|
}
|
|
|
|
int i;
|
|
pThread = NULL;
|
|
for (i = 0; i < (int)NuRecordGetNumThreads(pRecord); i++) {
|
|
pThread = NuGetThread(pRecord, i);
|
|
assert(pThread != NULL);
|
|
if (NuGetThreadID(pThread) == wantedThreadID)
|
|
break;
|
|
}
|
|
if (i == (int)NuRecordGetNumThreads(pRecord)) {
|
|
/* didn't find the thread we wanted */
|
|
pErrMsg->Format(L"searched %d threads but couldn't find 0x%02x",
|
|
NuRecordGetNumThreads(pRecord), which);
|
|
goto bail;
|
|
}
|
|
|
|
assert(pThread != NULL);
|
|
memcpy(pRetThread, pThread, sizeof(*pRetThread));
|
|
|
|
bail:
|
|
return;
|
|
}
|
|
|
|
|
|
//static const char* gShortFormatNames[] = {
|
|
// "unc", "squ", "lz1", "lz2", "u12", "u16", "dfl", "bzp"
|
|
//};
|
|
static const WCHAR* gFormatNames[] = {
|
|
L"Uncompr", L"Squeeze", L"LZW/1", L"LZW/2", L"LZC-12",
|
|
L"LZC-16", L"Deflate", L"Bzip2"
|
|
};
|
|
|
|
void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
|
|
{
|
|
/*
|
|
* The "best format" and "record type" stuff assume that the entire
|
|
* record contains only a disk thread or a file thread, and that any
|
|
* format is interesting so long as it isn't "no compression". In
|
|
* general these will be true, because ShrinkIt and NuLib create files
|
|
* this way.
|
|
*
|
|
* You could, of course, create a single record with a data thread and
|
|
* a disk image thread, but it's a fair bet ShrinkIt would ignore one
|
|
* or the other.
|
|
*
|
|
* NOTE: we don't currently work around the GSHK zero-length file bug.
|
|
* Such records, which have a filename thread but no data threads at all,
|
|
* will be categorized as "unknown". We could detect the situation and
|
|
* correct it, but we might as well flag it in a user-visible way.
|
|
*/
|
|
const NuThread* pThread;
|
|
NuThreadID threadID;
|
|
unsigned long idx;
|
|
RecordKind recordKind;
|
|
unsigned long uncompressedLen;
|
|
unsigned long compressedLen;
|
|
unsigned short format;
|
|
|
|
recordKind = kRecordKindUnknown;
|
|
uncompressedLen = compressedLen = 0;
|
|
format = kNuThreadFormatUncompressed;
|
|
|
|
for (idx = 0; idx < pRecord->recTotalThreads; idx++) {
|
|
pThread = NuGetThread(pRecord, idx);
|
|
ASSERT(pThread != NULL);
|
|
|
|
threadID = NuMakeThreadID(pThread->thThreadClass,
|
|
pThread->thThreadKind);
|
|
|
|
if (pThread->thThreadClass == kNuThreadClassData) {
|
|
/* replace what's there if this might be more interesting */
|
|
if (format == kNuThreadFormatUncompressed)
|
|
format = (unsigned short) pThread->thThreadFormat;
|
|
|
|
if (threadID == kNuThreadIDRsrcFork)
|
|
recordKind = kRecordKindForkedFile;
|
|
else if (threadID == kNuThreadIDDiskImage)
|
|
recordKind = kRecordKindDisk;
|
|
else if (threadID == kNuThreadIDDataFork &&
|
|
recordKind == kRecordKindUnknown)
|
|
recordKind = kRecordKindFile;
|
|
|
|
/* sum up, so we get both forks of forked files */
|
|
//uncompressedLen += pThread->actualThreadEOF;
|
|
compressedLen += pThread->thCompThreadEOF;
|
|
}
|
|
|
|
if (threadID == kNuThreadIDDataFork) {
|
|
if (!GetHasDataFork() && !GetHasDiskImage()) {
|
|
SetHasDataFork(true);
|
|
SetDataForkLen(pThread->actualThreadEOF);
|
|
} else {
|
|
LOGW("WARNING: ignoring second disk image / data fork");
|
|
}
|
|
}
|
|
if (threadID == kNuThreadIDRsrcFork) {
|
|
if (!GetHasRsrcFork()) {
|
|
SetHasRsrcFork(true);
|
|
SetRsrcForkLen(pThread->actualThreadEOF);
|
|
} else {
|
|
LOGW("WARNING: ignoring second data fork");
|
|
}
|
|
}
|
|
if (threadID == kNuThreadIDDiskImage) {
|
|
if (!GetHasDiskImage() && !GetHasDataFork()) {
|
|
SetHasDiskImage(true);
|
|
SetDataForkLen(pThread->actualThreadEOF);
|
|
} else {
|
|
LOGW("WARNING: ignoring second disk image / data fork");
|
|
}
|
|
}
|
|
if (threadID == kNuThreadIDComment) {
|
|
SetHasComment(true);
|
|
if (pThread->actualThreadEOF != 0)
|
|
SetHasNonEmptyComment(true);
|
|
}
|
|
}
|
|
|
|
SetRecordKind(recordKind);
|
|
//SetUncompressedLen(uncompressedLen);
|
|
SetCompressedLen(compressedLen);
|
|
|
|
if (format >= 0 && format < NELEM(gFormatNames))
|
|
SetFormatStr(gFormatNames[format]);
|
|
else
|
|
SetFormatStr(L"Unknown");
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*static*/ CString NufxArchive::AppInit(void)
|
|
{
|
|
NuError nerr;
|
|
CString result("");
|
|
int32_t major, minor, bug;
|
|
|
|
nerr = NuGetVersion(&major, &minor, &bug, NULL, NULL);
|
|
if (nerr != kNuErrNone) {
|
|
result = "Unable to get version number from NufxLib.";
|
|
goto bail;
|
|
}
|
|
|
|
if (major != kNuVersionMajor || minor < kNuVersionMinor) {
|
|
result.Format(L"Older or incompatible version of NufxLib DLL found.\r\r"
|
|
L"Wanted v%d.%d.x, found %ld.%ld.%ld.",
|
|
kNuVersionMajor, kNuVersionMinor,
|
|
major, minor, bug);
|
|
goto bail;
|
|
}
|
|
if (bug != kNuVersionBug) {
|
|
LOGI("Different 'bug' version (built vX.X.%d, dll vX.X.%d)",
|
|
kNuVersionBug, bug);
|
|
}
|
|
|
|
/* set NufxLib's global error message handler */
|
|
NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler);
|
|
|
|
bail:
|
|
return result;
|
|
}
|
|
|
|
/*static*/ bool NufxArchive::IsCompressionSupported(NuThreadFormat format)
|
|
{
|
|
NuFeature feature;
|
|
|
|
switch (format) {
|
|
case kNuThreadFormatUncompressed:
|
|
return true;
|
|
|
|
case kNuThreadFormatHuffmanSQ:
|
|
feature = kNuFeatureCompressSQ;
|
|
break;
|
|
case kNuThreadFormatLZW1:
|
|
case kNuThreadFormatLZW2:
|
|
feature = kNuFeatureCompressLZW;
|
|
break;
|
|
case kNuThreadFormatLZC12:
|
|
case kNuThreadFormatLZC16:
|
|
feature = kNuFeatureCompressLZC;
|
|
break;
|
|
case kNuThreadFormatDeflate:
|
|
feature = kNuFeatureCompressDeflate;
|
|
break;
|
|
case kNuThreadFormatBzip2:
|
|
feature = kNuFeatureCompressBzip2;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(false);
|
|
return false;
|
|
}
|
|
|
|
NuError nerr;
|
|
nerr = NuTestFeature(feature);
|
|
if (nerr == kNuErrNone)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
NuResult NufxArchive::NufxErrorMsgHandler(NuArchive*, void* vErrorMessage)
|
|
{
|
|
const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage;
|
|
|
|
LOG_BASE(pErrorMessage->isDebug ? DebugLog::LOG_DEBUG : DebugLog::LOG_WARN,
|
|
pErrorMessage->file, pErrorMessage->line, "<nufxlib> %hs",
|
|
pErrorMessage->message);
|
|
|
|
return kNuOK;
|
|
}
|
|
|
|
/*static*/NuResult NufxArchive::ProgressUpdater(NuArchive* pArchive,
|
|
void* vpProgress)
|
|
{
|
|
// "oldName" ends up on top, "newName" on bottom.
|
|
const NuProgressData* pProgress = (const NuProgressData*) vpProgress;
|
|
NufxArchive* pThis;
|
|
MainWindow* pMainWin = (MainWindow*)::AfxGetMainWnd();
|
|
int status;
|
|
const char* oldName;
|
|
const char* newName;
|
|
int perc;
|
|
|
|
ASSERT(pProgress != NULL);
|
|
ASSERT(pMainWin != NULL);
|
|
|
|
ASSERT(pArchive != NULL);
|
|
(void) NuGetExtraData(pArchive, (void**) &pThis);
|
|
ASSERT(pThis != NULL);
|
|
|
|
oldName = newName = NULL;
|
|
if (pProgress->operation == kNuOpAdd) {
|
|
oldName = pProgress->origPathnameUNI;
|
|
newName = pProgress->pathnameUNI;
|
|
if (pThis->fProgressAsRecompress)
|
|
oldName = "-";
|
|
} else if (pProgress->operation == kNuOpTest) {
|
|
oldName = pProgress->pathnameUNI;
|
|
} else if (pProgress->operation == kNuOpExtract) {
|
|
if (pThis->fProgressAsRecompress) {
|
|
oldName = pProgress->origPathnameUNI;
|
|
newName = "-";
|
|
}
|
|
}
|
|
|
|
perc = pProgress->percentComplete;
|
|
if (pProgress->state == kNuProgressDone)
|
|
perc = 100;
|
|
|
|
//LOGI("Progress: %d%% '%hs' '%hs'", perc,
|
|
// oldName == NULL ? "(null)" : oldName,
|
|
// newName == NULL ? "(null)" : newName);
|
|
|
|
//status = pMainWin->SetProgressUpdate(perc, oldName, newName);
|
|
CString oldNameW(oldName);
|
|
CString newNameW(newName);
|
|
status = SET_PROGRESS_UPDATE2(perc, oldNameW, newNameW);
|
|
|
|
/* check to see if user hit the "cancel" button on the progress dialog */
|
|
if (pProgress->state == kNuProgressAborted) {
|
|
LOGI("(looks like we're aborting)");
|
|
ASSERT(status == IDCANCEL);
|
|
}
|
|
|
|
if (status == IDCANCEL) {
|
|
LOGI("Signaling NufxLib to abort");
|
|
return kNuAbort;
|
|
} else
|
|
return kNuOK;
|
|
}
|
|
|
|
GenericArchive::OpenResult NufxArchive::Open(const WCHAR* filename,
|
|
bool readOnly, CString* pErrMsg)
|
|
{
|
|
NuError nerr;
|
|
CString errMsg;
|
|
|
|
ASSERT(fpArchive == NULL);
|
|
|
|
CStringA filenameA(filename);
|
|
if (!readOnly) {
|
|
CString tmpname = GenDerivedTempName(filename);
|
|
LOGI("Opening file '%ls' rw (tmp='%ls')", filename, (LPCWSTR) tmpname);
|
|
fIsReadOnly = false;
|
|
CStringA tmpnameA(tmpname);
|
|
nerr = NuOpenRW(filenameA, tmpnameA, 0, &fpArchive);
|
|
|
|
if (nerr == kNuErrFileAccessDenied || nerr == EACCES) {
|
|
LOGI("Read-write failed with access denied, trying read-only");
|
|
readOnly = true;
|
|
}
|
|
}
|
|
if (readOnly) {
|
|
LOGI("Opening file '%ls' ro", (LPCWSTR) filename);
|
|
fIsReadOnly = true;
|
|
nerr = NuOpenRO(filenameA, &fpArchive);
|
|
}
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to open '%ls': %hs", filename, NuStrError(nerr));
|
|
goto bail;
|
|
} else {
|
|
//LOGI("FILE OPEN SUCCESS");
|
|
}
|
|
|
|
nerr = SetCallbacks();
|
|
if (nerr != kNuErrNone) {
|
|
errMsg = L"Callback init failed";
|
|
goto bail;
|
|
}
|
|
|
|
nerr = LoadContents();
|
|
if (nerr != kNuErrNone) {
|
|
errMsg = L"Failed reading archive contents: ";
|
|
errMsg += NuStrError(nerr);
|
|
}
|
|
|
|
SetPathName(filename);
|
|
|
|
bail:
|
|
*pErrMsg = errMsg;
|
|
if (!errMsg.IsEmpty())
|
|
return kResultFailure;
|
|
else
|
|
return kResultSuccess;
|
|
}
|
|
|
|
CString NufxArchive::New(const WCHAR* filename, const void* options)
|
|
{
|
|
NuError nerr;
|
|
CString retmsg;
|
|
|
|
ASSERT(fpArchive == NULL);
|
|
ASSERT(options == NULL);
|
|
|
|
CString tmpname = GenDerivedTempName(filename);
|
|
LOGI("Creating file '%ls' (tmp='%ls')", filename, (LPCWSTR) tmpname);
|
|
fIsReadOnly = false;
|
|
CStringA filenameA(filename);
|
|
CStringA tmpnameA(tmpname);
|
|
nerr = NuOpenRW(filenameA, tmpnameA, kNuOpenCreat | kNuOpenExcl, &fpArchive);
|
|
if (nerr != kNuErrNone) {
|
|
retmsg.Format(L"Unable to open '%ls': %hs", filename, NuStrError(nerr));
|
|
goto bail;
|
|
} else {
|
|
LOGI("NEW FILE SUCCESS");
|
|
}
|
|
|
|
|
|
nerr = SetCallbacks();
|
|
if (nerr != kNuErrNone) {
|
|
retmsg = L"Callback init failed";
|
|
goto bail;
|
|
}
|
|
|
|
SetPathName(filename);
|
|
|
|
bail:
|
|
return retmsg;
|
|
}
|
|
|
|
NuError NufxArchive::SetCallbacks(void)
|
|
{
|
|
NuError nerr;
|
|
|
|
nerr = NuSetExtraData(fpArchive, this);
|
|
if (nerr != kNuErrNone)
|
|
goto bail;
|
|
// nerr = NuSetSelectionFilter(fpArchive, SelectionFilter);
|
|
// if (nerr != kNuErrNone)
|
|
// goto bail;
|
|
// nerr = NuSetOutputPathnameFilter(fpArchive, OutputPathnameFilter);
|
|
// if (nerr != kNuErrNone)
|
|
// goto bail;
|
|
NuSetProgressUpdater(fpArchive, ProgressUpdater);
|
|
// nerr = NuSetErrorHandler(fpArchive, ErrorHandler);
|
|
// if (nerr != kNuErrNone)
|
|
// goto bail;
|
|
NuSetErrorMessageHandler(fpArchive, NufxErrorMsgHandler);
|
|
|
|
/* let NufxLib worry about buggy records without data threads */
|
|
nerr = NuSetValue(fpArchive, kNuValueMaskDataless, kNuValueTrue);
|
|
if (nerr != kNuErrNone)
|
|
goto bail;
|
|
|
|
/* set any values based on Preferences values */
|
|
PreferencesChanged();
|
|
|
|
bail:
|
|
return nerr;
|
|
}
|
|
|
|
void NufxArchive::PreferencesChanged(void)
|
|
{
|
|
NuError nerr;
|
|
const Preferences* pPreferences = GET_PREFERENCES();
|
|
bool val;
|
|
|
|
val = pPreferences->GetPrefBool(kPrMimicShrinkIt);
|
|
nerr = NuSetValue(fpArchive, kNuValueMimicSHK, val);
|
|
if (nerr != kNuErrNone) {
|
|
LOGI("NuSetValue(kNuValueMimicSHK, %d) failed, err=%d", val, nerr);
|
|
ASSERT(false);
|
|
} else {
|
|
LOGI("Set MimicShrinkIt to %d", val);
|
|
}
|
|
|
|
val = pPreferences->GetPrefBool(kPrReduceSHKErrorChecks);
|
|
NuSetValue(fpArchive, kNuValueIgnoreLZW2Len, val);
|
|
NuSetValue(fpArchive, kNuValueIgnoreCRC, val);
|
|
|
|
val = pPreferences->GetPrefBool(kPrBadMacSHK);
|
|
NuSetValue(fpArchive, kNuValueHandleBadMac, val);
|
|
}
|
|
|
|
long NufxArchive::GetCapability(Capability cap)
|
|
{
|
|
switch (cap) {
|
|
case kCapCanTest:
|
|
return true;
|
|
break;
|
|
case kCapCanRenameFullPath:
|
|
return true;
|
|
break;
|
|
case kCapCanRecompress:
|
|
return true;
|
|
break;
|
|
case kCapCanEditComment:
|
|
return true;
|
|
break;
|
|
case kCapCanAddDisk:
|
|
return true;
|
|
break;
|
|
case kCapCanConvEOLOnAdd:
|
|
return false;
|
|
break;
|
|
case kCapCanCreateSubdir:
|
|
return false;
|
|
break;
|
|
case kCapCanRenameVolume:
|
|
return false;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
NuError NufxArchive::LoadContents(void)
|
|
{
|
|
/*
|
|
* We will need to set an error handler if we want to be able to do things
|
|
* like "I found a bad CRC, did you want me to keep trying anyway?".
|
|
*/
|
|
long counter = 0;
|
|
NuError result;
|
|
|
|
LOGI("NufxArchive LoadContents");
|
|
ASSERT(fpArchive != NULL);
|
|
|
|
{
|
|
MainWindow* pMain = GET_MAIN_WINDOW();
|
|
ExclusiveModelessDialog* pWaitDlg = new ExclusiveModelessDialog;
|
|
pWaitDlg->Create(IDD_LOADING, pMain);
|
|
pWaitDlg->CenterWindow();
|
|
pMain->PeekAndPump(); // redraw
|
|
CWaitCursor waitc;
|
|
|
|
result = NuContents(fpArchive, ContentFunc);
|
|
|
|
SET_PROGRESS_COUNTER(-1);
|
|
|
|
pWaitDlg->DestroyWindow();
|
|
//pMain->PeekAndPump(); // redraw
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CString NufxArchive::Reload(void)
|
|
{
|
|
NuError nerr;
|
|
CString errMsg;
|
|
|
|
fReloadFlag = true; // tell everybody that cached data is invalid
|
|
|
|
DeleteEntries(); // a GenericArchive operation
|
|
|
|
nerr = LoadContents();
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"ERROR: unable to reload archive contents: %hs.",
|
|
NuStrError(nerr));
|
|
|
|
DeleteEntries();
|
|
fIsReadOnly = true;
|
|
}
|
|
|
|
return errMsg;
|
|
}
|
|
|
|
NuError NufxArchive::InternalReload(CWnd* pMsgWnd)
|
|
{
|
|
CString errMsg;
|
|
|
|
errMsg = Reload();
|
|
|
|
if (!errMsg.IsEmpty()) {
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
return kNuErrGeneric;
|
|
}
|
|
|
|
return kNuErrNone;
|
|
}
|
|
|
|
NuResult NufxArchive::ContentFunc(NuArchive* pArchive, void* vpRecord)
|
|
{
|
|
const NuRecord* pRecord = (const NuRecord*) vpRecord;
|
|
NufxArchive* pThis;
|
|
NufxEntry* pNewEntry;
|
|
|
|
ASSERT(pArchive != NULL);
|
|
ASSERT(vpRecord != NULL);
|
|
|
|
NuGetExtraData(pArchive, (void**) &pThis);
|
|
|
|
pNewEntry = new NufxEntry(pArchive);
|
|
|
|
pNewEntry->SetPathNameMOR(pRecord->filenameMOR);
|
|
pNewEntry->SetFssep(NuGetSepFromSysInfo(pRecord->recFileSysInfo));
|
|
pNewEntry->SetFileType(pRecord->recFileType);
|
|
pNewEntry->SetAuxType(pRecord->recExtraType);
|
|
pNewEntry->SetAccess(pRecord->recAccess);
|
|
pNewEntry->SetCreateWhen(DateTimeToSeconds(&pRecord->recCreateWhen));
|
|
pNewEntry->SetModWhen(DateTimeToSeconds(&pRecord->recModWhen));
|
|
|
|
/*
|
|
* Our files are always ProDOS format. This is especially important
|
|
* when cutting & pasting, so that the DOS high ASCII converter gets
|
|
* invoked at appropriate times.
|
|
*/
|
|
pNewEntry->SetSourceFS(DiskImg::kFormatProDOS);
|
|
|
|
pNewEntry->AnalyzeRecord(pRecord);
|
|
pNewEntry->SetRecordIdx(pRecord->recordIdx);
|
|
|
|
pThis->AddEntry(pNewEntry);
|
|
if ((pThis->GetNumEntries() % 10) == 0)
|
|
SET_PROGRESS_COUNTER(pThis->GetNumEntries());
|
|
|
|
return kNuOK;
|
|
}
|
|
|
|
/*static*/ time_t NufxArchive::DateTimeToSeconds(const NuDateTime* pDateTime)
|
|
{
|
|
if (pDateTime->second == 0 &&
|
|
pDateTime->minute == 0 &&
|
|
pDateTime->hour == 0 &&
|
|
pDateTime->year == 0 &&
|
|
pDateTime->day == 0 &&
|
|
pDateTime->month == 0 &&
|
|
pDateTime->extra == 0 &&
|
|
pDateTime->weekDay == 0)
|
|
{
|
|
return kDateNone;
|
|
}
|
|
|
|
int year;
|
|
if (pDateTime->year < 40)
|
|
year = pDateTime->year + 2000;
|
|
else
|
|
year = pDateTime->year + 1900;
|
|
|
|
if (year < 1969) {
|
|
/*
|
|
* Years like 1963 are valid on an Apple II but cannot be represented
|
|
* as a time_t, which starts in 1970. (Depending on GMT offsets,
|
|
* it actually starts a few hours earlier at the end of 1969.)
|
|
*
|
|
* I'm catching this here because of an assert in the CTime
|
|
* constructor. The constructor seems to do the right thing, and the
|
|
* assert won't be present in the shipping version, but it's annoying
|
|
* during debugging.
|
|
*/
|
|
//LOGI(" Ignoring funky year %ld", year);
|
|
return kDateInvalid;
|
|
}
|
|
if (pDateTime->month > 11)
|
|
return kDateInvalid;
|
|
|
|
CTime modTime(year,
|
|
pDateTime->month+1,
|
|
pDateTime->day+1,
|
|
pDateTime->hour,
|
|
pDateTime->minute,
|
|
pDateTime->second);
|
|
return (time_t) modTime.GetTime();
|
|
}
|
|
|
|
/*static*/ NuResult NufxArchive::ArrayDeleteHandler(NuArchive* pArchive, void* ptr)
|
|
{
|
|
delete[] ptr;
|
|
return kNuOK;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- add files (or disks)
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool NufxArchive::BulkAdd(ActionProgressDialog* pActionProgress,
|
|
const AddFilesDialog* pAddOpts)
|
|
{
|
|
/*
|
|
* This calls into the GenericArchive "AddFile" function, which does
|
|
* Win32-specific processing. That function calls our DoAddFile function,
|
|
* which does the NuFX stuff.
|
|
*/
|
|
NuError nerr;
|
|
CString errMsg;
|
|
WCHAR curDir[MAX_PATH] = L"";
|
|
bool retVal = false;
|
|
|
|
LOGI("Opts: '%ls' typePres=%d", (LPCWSTR) pAddOpts->fStoragePrefix,
|
|
pAddOpts->fTypePreservation);
|
|
LOGI(" sub=%d strip=%d ovwr=%d",
|
|
pAddOpts->fIncludeSubfolders, pAddOpts->fStripFolderNames,
|
|
pAddOpts->fOverwriteExisting);
|
|
|
|
AddPrep(pActionProgress, pAddOpts);
|
|
|
|
pActionProgress->SetArcName(L"(Scanning files to be added...)");
|
|
pActionProgress->SetFileName(L"");
|
|
|
|
/* initialize count */
|
|
fNumAdded = 0;
|
|
|
|
const CString& directory = pAddOpts->GetDirectory();
|
|
LOGI("Selected path = '%ls'", (LPCWSTR) directory);
|
|
|
|
if (GetCurrentDirectory(NELEM(curDir), curDir) == 0) {
|
|
errMsg = L"Unable to get current directory.\n";
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
if (SetCurrentDirectory(directory) == false) {
|
|
errMsg.Format(L"Unable to set current directory to '%ls'.\n",
|
|
(LPCWSTR) directory);
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
const CStringArray& fileNames = pAddOpts->GetFileNames();
|
|
for (int i = 0; i < fileNames.GetCount(); i++) {
|
|
const CString& name = fileNames.GetAt(i);
|
|
LOGI(" file '%ls'", (LPCWSTR) name);
|
|
|
|
/* this just provides the list of files to NufxLib */
|
|
nerr = AddFile(pAddOpts, name, &errMsg);
|
|
if (nerr != kNuErrNone) {
|
|
if (errMsg.IsEmpty())
|
|
errMsg.Format(L"Failed while adding file '%ls': %hs.",
|
|
(LPCWSTR) name, NuStrError(nerr));
|
|
if (nerr != kNuErrAborted) {
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* actually do the work */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Unable to add files: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
goto bail;
|
|
}
|
|
|
|
if (!fNumAdded) {
|
|
errMsg = L"No files added.\n";
|
|
fpMsgWnd->MessageBox(errMsg, L"CiderPress", MB_OK | MB_ICONWARNING);
|
|
} else {
|
|
if (InternalReload(fpMsgWnd) == kNuErrNone)
|
|
retVal = true;
|
|
else
|
|
errMsg = L"Reload failed.";
|
|
}
|
|
|
|
bail:
|
|
NuAbort(fpArchive); // abort anything that didn't get flushed
|
|
if (SetCurrentDirectory(curDir) == false) {
|
|
errMsg.Format(L"Unable to reset current directory to '%ls'.\n", curDir);
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
// bummer, but don't signal failure
|
|
}
|
|
AddFinish();
|
|
return retVal;
|
|
}
|
|
|
|
bool NufxArchive::AddDisk(ActionProgressDialog* pActionProgress,
|
|
const AddFilesDialog* pAddOpts)
|
|
{
|
|
NuError nerr;
|
|
CString errMsg;
|
|
const int kBlockSize = 512;
|
|
PathProposal pathProp;
|
|
PathName pathName;
|
|
DiskImg* pDiskImg;
|
|
NuDataSource* pSource = NULL;
|
|
unsigned char* diskData = NULL;
|
|
WCHAR curDir[MAX_PATH] = L"\\";
|
|
bool retVal = false;
|
|
CStringA storageNameA;
|
|
|
|
LOGI("AddDisk: '%ls' (count=%d)", (LPCWSTR) pAddOpts->GetDirectory(),
|
|
pAddOpts->GetFileNames().GetCount());
|
|
LOGI("Opts: stpfx='%ls' pres=%d", (LPCWSTR) pAddOpts->fStoragePrefix,
|
|
pAddOpts->fTypePreservation);
|
|
LOGI(" sub=%d strip=%d ovwr=%d",
|
|
pAddOpts->fIncludeSubfolders, pAddOpts->fStripFolderNames,
|
|
pAddOpts->fOverwriteExisting);
|
|
|
|
if (pAddOpts->GetFileNames().GetCount() != 1) {
|
|
LOGW("GLITCH: expected only one filename, found %d",
|
|
pAddOpts->GetFileNames().GetCount());
|
|
goto bail;
|
|
}
|
|
|
|
pDiskImg = pAddOpts->fpDiskImg;
|
|
ASSERT(pDiskImg != NULL);
|
|
|
|
/* allocate storage for the entire disk */
|
|
diskData = new uint8_t[pDiskImg->GetNumBlocks() * kBlockSize];
|
|
if (diskData == NULL) {
|
|
errMsg.Format(L"Unable to allocate %d bytes.",
|
|
pDiskImg->GetNumBlocks() * kBlockSize);
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
/* prepare to add */
|
|
AddPrep(pActionProgress, pAddOpts);
|
|
|
|
const CString& directory = pAddOpts->GetDirectory();
|
|
const CStringArray& fileNames = pAddOpts->GetFileNames();
|
|
LOGD("Selected path = '%ls'", (LPCWSTR) directory);
|
|
|
|
if (GetCurrentDirectory(NELEM(curDir), curDir) == 0) {
|
|
errMsg = L"Unable to get current directory.\n";
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
if (SetCurrentDirectory(directory) == false) {
|
|
errMsg.Format(L"Unable to set current directory to '%ls'.\n",
|
|
(LPCWSTR) directory);
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
const CString& fileName = pAddOpts->GetFileNames().GetAt(0);
|
|
LOGI(" file '%ls'", (LPCWSTR) fileName);
|
|
|
|
/* strip off preservation stuff, and ignore it */
|
|
pathProp.Init(fileName);
|
|
pathProp.fStripDiskImageSuffix = true;
|
|
pathProp.LocalToArchive(pAddOpts);
|
|
|
|
/* fill in the necessary file details */
|
|
NuFileDetails details;
|
|
memset(&details, 0, sizeof(details));
|
|
details.threadID = kNuThreadIDDiskImage;
|
|
details.storageType = kBlockSize;
|
|
details.access = kNuAccessUnlocked;
|
|
details.extraType = pAddOpts->fpDiskImg->GetNumBlocks();
|
|
storageNameA = pathProp.fStoredPathName; // TODO(Unicode)
|
|
details.origName = (LPCWSTR) fileName; // pass wide string through
|
|
details.storageNameMOR = storageNameA;
|
|
details.fileSysID = kNuFileSysUnknown;
|
|
details.fileSysInfo = PathProposal::kDefaultStoredFssep;
|
|
|
|
time_t now, then;
|
|
|
|
pathName.SetPathName(fileName);
|
|
now = time(NULL);
|
|
then = pathName.GetModWhen();
|
|
UNIXTimeToDateTime(&now, &details.archiveWhen);
|
|
UNIXTimeToDateTime(&then, &details.modWhen);
|
|
UNIXTimeToDateTime(&then, &details.createWhen);
|
|
|
|
/* set up the progress updater */
|
|
pActionProgress->SetArcName(pathProp.fStoredPathName);
|
|
pActionProgress->SetFileName(fileName);
|
|
|
|
/* read the disk now that we have progress update titles in place */
|
|
int block, numBadBlocks;
|
|
unsigned char* bufPtr;
|
|
numBadBlocks = 0;
|
|
for (block = 0, bufPtr = diskData; block < pDiskImg->GetNumBlocks(); block++)
|
|
{
|
|
DIError dierr;
|
|
dierr = pDiskImg->ReadBlock(block, bufPtr);
|
|
if (dierr != kDIErrNone)
|
|
numBadBlocks++;
|
|
bufPtr += kBlockSize;
|
|
}
|
|
if (numBadBlocks > 0) {
|
|
CString appName, msg;
|
|
CheckedLoadString(&appName, IDS_MB_APP_NAME);
|
|
msg.Format(L"Skipped %ld unreadable block%ls.", numBadBlocks,
|
|
numBadBlocks == 1 ? L"" : L"s");
|
|
fpMsgWnd->MessageBox(msg, appName, MB_OK | MB_ICONWARNING);
|
|
// keep going -- just a warning
|
|
}
|
|
|
|
/* create a data source for the disk */
|
|
nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, 0,
|
|
diskData, 0, pAddOpts->fpDiskImg->GetNumBlocks() * kBlockSize,
|
|
NULL, &pSource);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg = "Unable to create NufxLib data source.";
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
/* add the record; name conflicts cause the error handler to fire */
|
|
NuRecordIdx recordIdx;
|
|
nerr = NuAddRecord(fpArchive, &details, &recordIdx);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Failed adding record: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
goto bail;
|
|
}
|
|
|
|
/* do the compression */
|
|
nerr = NuAddThread(fpArchive, recordIdx, kNuThreadIDDiskImage,
|
|
pSource, NULL);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Failed adding thread: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
pSource = NULL; /* NufxLib owns it now */
|
|
|
|
/* actually do the work */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Unable to add disk: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
goto bail;
|
|
}
|
|
|
|
if (InternalReload(fpMsgWnd) == kNuErrNone)
|
|
retVal = true;
|
|
|
|
bail:
|
|
delete[] diskData;
|
|
NuAbort(fpArchive); // abort anything that didn't get flushed
|
|
NuFreeDataSource(pSource);
|
|
if (SetCurrentDirectory(curDir) == false) {
|
|
errMsg.Format(L"Unable to reset current directory to '%ls'.\n", curDir);
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
// bummer
|
|
}
|
|
AddFinish();
|
|
return retVal;
|
|
}
|
|
|
|
NuError NufxArchive::DoAddFile(const AddFilesDialog* pAddOpts,
|
|
LocalFileDetails* pDetails)
|
|
{
|
|
NuError err;
|
|
NuRecordIdx recordIdx = 0;
|
|
|
|
do {
|
|
// Must re-get the NuFileDetails, because updating the filename for
|
|
// rename will invalidate the previous version.
|
|
const NuFileDetails& nuFileDetails = pDetails->GetNuFileDetails();
|
|
|
|
// TODO(Unicode): NufxLib should accept wide pathname
|
|
CStringA origNameA(pDetails->GetLocalPathName());
|
|
CString dummyMsg;
|
|
if (!PathName::TestNarrowConversion(pDetails->GetLocalPathName(),
|
|
origNameA, &dummyMsg)) {
|
|
err = kNuErrInvalidFilename;
|
|
goto bail_quiet;
|
|
}
|
|
err = NuAddFile(fpArchive, origNameA, &nuFileDetails, false, &recordIdx);
|
|
|
|
if (err == kNuErrNone) {
|
|
fNumAdded++;
|
|
} else if (err == kNuErrSkipped) {
|
|
/* "maybe overwrite" UI causes this if user declines */
|
|
// fall through with the error
|
|
LOGI("DoAddFile: skipped '%ls'", (LPCWSTR) pDetails->GetLocalPathName());
|
|
} else if (err == kNuErrRecordExists) {
|
|
AddClashDialog dlg;
|
|
|
|
dlg.fWindowsName = pDetails->GetLocalPathName();
|
|
dlg.fStorageName = pDetails->GetStrippedLocalPathName();
|
|
if (dlg.DoModal() != IDOK) {
|
|
err = kNuErrAborted;
|
|
goto bail_quiet;
|
|
}
|
|
if (dlg.fDoRename) {
|
|
LOGD("add clash: rename to '%ls'", (LPCWSTR) dlg.fNewName);
|
|
pDetails->SetStrippedLocalPathName(dlg.fNewName);
|
|
continue;
|
|
} else {
|
|
LOGD("add clash: skip");
|
|
err = kNuErrSkipped;
|
|
// fall through with error
|
|
}
|
|
}
|
|
} while (false);
|
|
|
|
if (err != kNuErrNone && err != kNuErrAborted && err != kNuErrSkipped) {
|
|
CString msg;
|
|
msg.Format(L"Unable to add file '%ls': %hs.",
|
|
(LPCWSTR) pDetails->GetLocalPathName(), NuStrError(err));
|
|
ShowFailureMsg(fpMsgWnd, msg, IDS_FAILED);
|
|
}
|
|
bail_quiet:
|
|
return err;
|
|
}
|
|
|
|
void NufxArchive::AddPrep(CWnd* pMsgWnd, const AddFilesDialog* pAddOpts)
|
|
{
|
|
NuError nerr;
|
|
const Preferences* pPreferences = GET_PREFERENCES();
|
|
int defaultCompression;
|
|
|
|
ASSERT(fpArchive != NULL);
|
|
|
|
fpMsgWnd = pMsgWnd;
|
|
ASSERT(fpMsgWnd != NULL);
|
|
|
|
fpAddOpts = pAddOpts;
|
|
ASSERT(fpAddOpts != NULL);
|
|
|
|
//fBulkProgress = true;
|
|
|
|
defaultCompression = pPreferences->GetPrefLong(kPrCompressionType);
|
|
nerr = NuSetValue(fpArchive, kNuValueDataCompression,
|
|
defaultCompression + kNuCompressNone);
|
|
if (nerr != kNuErrNone) {
|
|
LOGI("GLITCH: unable to set compression type to %d",
|
|
defaultCompression);
|
|
/* keep going */
|
|
}
|
|
|
|
if (pAddOpts->fOverwriteExisting)
|
|
NuSetValue(fpArchive, kNuValueHandleExisting, kNuAlwaysOverwrite);
|
|
else
|
|
NuSetValue(fpArchive, kNuValueHandleExisting, kNuMaybeOverwrite);
|
|
|
|
NuSetErrorHandler(fpArchive, BulkAddErrorHandler);
|
|
NuSetExtraData(fpArchive, this);
|
|
}
|
|
|
|
void NufxArchive::AddFinish(void)
|
|
{
|
|
NuSetErrorHandler(fpArchive, NULL);
|
|
NuSetValue(fpArchive, kNuValueHandleExisting, kNuMaybeOverwrite);
|
|
fpMsgWnd = NULL;
|
|
fpAddOpts = NULL;
|
|
//fBulkProgress = false;
|
|
}
|
|
|
|
/*static*/ NuResult NufxArchive::BulkAddErrorHandler(NuArchive* pArchive,
|
|
void* vErrorStatus)
|
|
{
|
|
const NuErrorStatus* pErrorStatus = (const NuErrorStatus*)vErrorStatus;
|
|
NufxArchive* pThis;
|
|
NuResult result;
|
|
|
|
ASSERT(pArchive != NULL);
|
|
(void) NuGetExtraData(pArchive, (void**) &pThis);
|
|
ASSERT(pThis != NULL);
|
|
ASSERT(pArchive == pThis->fpArchive);
|
|
|
|
/* default action is to abort the current operation */
|
|
result = kNuAbort;
|
|
|
|
/*
|
|
* When adding files, the NuAddFile and NuAddRecord calls can return
|
|
* immediate, specific results for a single add. The only reasons for
|
|
* calling here are to decide if an existing record should be replaced
|
|
* or not (without even an option to rename), or to decide what to do
|
|
* when the NuFlush call runs into a problem while adding a file.
|
|
*/
|
|
if (pErrorStatus->operation != kNuOpAdd) {
|
|
ASSERT(false);
|
|
return kNuAbort;
|
|
}
|
|
|
|
if (pErrorStatus->err == kNuErrRecordExists) {
|
|
/* if they want to update or freshen, don't hassle them */
|
|
//if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState))
|
|
if (pThis->fpAddOpts->fOverwriteExisting) {
|
|
ASSERT(false); // should be handled by AddPrep()/NufxLib
|
|
result = kNuOverwrite;
|
|
} else
|
|
result = pThis->HandleReplaceExisting(pErrorStatus);
|
|
} else if (pErrorStatus->err == kNuErrFileNotFound) {
|
|
/* file was specified with NuAdd but removed during NuFlush */
|
|
result = pThis->HandleAddNotFound(pErrorStatus);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NuResult NufxArchive::HandleReplaceExisting(const NuErrorStatus* pErrorStatus)
|
|
{
|
|
NuResult result = kNuOK;
|
|
|
|
ASSERT(pErrorStatus != NULL);
|
|
ASSERT(pErrorStatus->pathnameUNI != NULL);
|
|
|
|
ASSERT(pErrorStatus->canOverwrite);
|
|
ASSERT(pErrorStatus->canSkip);
|
|
ASSERT(pErrorStatus->canAbort);
|
|
ASSERT(!pErrorStatus->canRename); // TODO: remember why we can't rename
|
|
|
|
/* no firm policy, ask the user */
|
|
ConfirmOverwriteDialog confOvwr;
|
|
PathName path(pErrorStatus->pathnameUNI);
|
|
|
|
// TODO(Unicode): convert MOR to Unicode
|
|
confOvwr.fExistingFile = pErrorStatus->pRecord->filenameMOR;
|
|
confOvwr.fExistingFileModWhen =
|
|
DateTimeToSeconds(&pErrorStatus->pRecord->recModWhen);
|
|
if (pErrorStatus->origPathname != NULL) {
|
|
confOvwr.fNewFileSource = (WCHAR*) pErrorStatus->origPathname;
|
|
PathName checkPath(confOvwr.fNewFileSource);
|
|
confOvwr.fNewFileModWhen = checkPath.GetModWhen();
|
|
} else {
|
|
confOvwr.fNewFileSource = L"???";
|
|
confOvwr.fNewFileModWhen = kDateNone;
|
|
}
|
|
|
|
confOvwr.fAllowRename = false;
|
|
if (confOvwr.DoModal() == IDCANCEL) {
|
|
result = kNuAbort;
|
|
goto bail;
|
|
}
|
|
if (confOvwr.fResultRename) {
|
|
ASSERT(false);
|
|
result = kNuAbort;
|
|
goto bail;
|
|
}
|
|
if (confOvwr.fResultApplyToAll) {
|
|
if (confOvwr.fResultOverwrite) {
|
|
(void) NuSetValue(fpArchive, kNuValueHandleExisting,
|
|
kNuAlwaysOverwrite);
|
|
} else {
|
|
(void) NuSetValue(fpArchive, kNuValueHandleExisting,
|
|
kNuNeverOverwrite);
|
|
}
|
|
}
|
|
if (confOvwr.fResultOverwrite)
|
|
result = kNuOverwrite;
|
|
else
|
|
result = kNuSkip;
|
|
|
|
bail:
|
|
return result;
|
|
}
|
|
|
|
NuResult NufxArchive::HandleAddNotFound(const NuErrorStatus* pErrorStatus)
|
|
{
|
|
CString errMsg;
|
|
|
|
errMsg.Format(L"Failed while adding '%hs': file no longer exists.",
|
|
pErrorStatus->pathnameUNI);
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
|
|
return kNuAbort;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- test files
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool NufxArchive::TestSelection(CWnd* pMsgWnd, SelectionSet* pSelSet)
|
|
{
|
|
NuError nerr;
|
|
NufxEntry* pEntry;
|
|
CString errMsg;
|
|
bool retVal = false;
|
|
|
|
ASSERT(fpArchive != NULL);
|
|
|
|
LOGI("Testing %d entries", pSelSet->GetNumEntries());
|
|
|
|
SelectionEntry* pSelEntry = pSelSet->IterNext();
|
|
while (pSelEntry != NULL) {
|
|
pEntry = (NufxEntry*) pSelEntry->GetEntry();
|
|
|
|
LOGI(" Testing %ld '%ls'", pEntry->GetRecordIdx(),
|
|
(LPCWSTR) pEntry->GetPathNameUNI());
|
|
nerr = NuTestRecord(fpArchive, pEntry->GetRecordIdx());
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr == kNuErrAborted) {
|
|
CString title;
|
|
CheckedLoadString(&title, IDS_MB_APP_NAME);
|
|
errMsg = "Cancelled.";
|
|
pMsgWnd->MessageBox(errMsg, title, MB_OK);
|
|
} else {
|
|
errMsg.Format(L"Failed while testing '%ls': %hs.",
|
|
(LPCWSTR) pEntry->GetPathNameUNI(), NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
goto bail;
|
|
}
|
|
|
|
pSelEntry = pSelSet->IterNext();
|
|
}
|
|
|
|
/* show success message */
|
|
errMsg.Format(L"Tested %d file%ls, no errors found.",
|
|
pSelSet->GetNumEntries(),
|
|
pSelSet->GetNumEntries() == 1 ? L"" : L"s");
|
|
pMsgWnd->MessageBox(errMsg);
|
|
retVal = true;
|
|
|
|
bail:
|
|
return retVal;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- delete files
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool NufxArchive::DeleteSelection(CWnd* pMsgWnd, SelectionSet* pSelSet)
|
|
{
|
|
NuError nerr;
|
|
NufxEntry* pEntry;
|
|
CString errMsg;
|
|
bool retVal = false;
|
|
|
|
ASSERT(fpArchive != NULL);
|
|
|
|
LOGI("Deleting %d entries", pSelSet->GetNumEntries());
|
|
|
|
/* mark entries for deletion */
|
|
SelectionEntry* pSelEntry = pSelSet->IterNext();
|
|
while (pSelEntry != NULL) {
|
|
pEntry = (NufxEntry*) pSelEntry->GetEntry();
|
|
|
|
LOGI(" Deleting %ld '%ls'", pEntry->GetRecordIdx(),
|
|
(LPCWSTR) pEntry->GetPathNameUNI());
|
|
nerr = NuDeleteRecord(fpArchive, pEntry->GetRecordIdx());
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to delete record %d: %hs.",
|
|
pEntry->GetRecordIdx(), NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
pSelEntry = pSelSet->IterNext();
|
|
}
|
|
|
|
/* actually do the delete */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to delete all files: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
}
|
|
|
|
if (InternalReload(fpMsgWnd) == kNuErrNone)
|
|
retVal = true;
|
|
|
|
bail:
|
|
return retVal;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- rename files
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool NufxArchive::RenameSelection(CWnd* pMsgWnd, SelectionSet* pSelSet)
|
|
{
|
|
CString errMsg;
|
|
NuError nerr;
|
|
bool retVal = false;
|
|
|
|
ASSERT(fpArchive != NULL);
|
|
|
|
LOGI("Renaming %d entries", pSelSet->GetNumEntries());
|
|
|
|
/*
|
|
* Figure out if we're allowed to change the entire path. (This is
|
|
* doing it the hard way, but what the hell.)
|
|
*/
|
|
long cap = GetCapability(GenericArchive::kCapCanRenameFullPath);
|
|
bool renameFullPath = (cap != 0);
|
|
|
|
LOGI("Rename, fullpath=%d", renameFullPath);
|
|
|
|
/*
|
|
* For each item in the selection set, bring up the "rename" dialog,
|
|
* and ask the GenericEntry to process it.
|
|
*
|
|
* If they hit "cancel" or there's an error, we still flush the
|
|
* previous changes. This is so that we don't have to create the
|
|
* same sort of deferred-write feature when renaming things in other
|
|
* sorts of archives (e.g. disk archives).
|
|
*/
|
|
SelectionEntry* pSelEntry = pSelSet->IterNext();
|
|
while (pSelEntry != NULL) {
|
|
NufxEntry* pEntry = (NufxEntry*) pSelEntry->GetEntry();
|
|
LOGD(" Renaming '%ls'", (LPCWSTR) pEntry->GetPathNameUNI());
|
|
|
|
RenameEntryDialog renameDlg(pMsgWnd);
|
|
renameDlg.SetCanRenameFullPath(renameFullPath);
|
|
renameDlg.SetCanChangeFssep(true);
|
|
renameDlg.fOldName = pEntry->GetPathNameUNI();
|
|
renameDlg.fFssep = pEntry->GetFssep();
|
|
renameDlg.fpArchive = this;
|
|
renameDlg.fpEntry = pEntry;
|
|
|
|
int result = renameDlg.DoModal();
|
|
if (result == IDOK) {
|
|
if (renameDlg.fFssep == '\0')
|
|
renameDlg.fFssep = kNufxNoFssep;
|
|
CStringA newNameA(renameDlg.fNewName);
|
|
nerr = NuRename(fpArchive, pEntry->GetRecordIdx(),
|
|
newNameA, renameDlg.fFssep);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to rename '%ls': %hs.",
|
|
(LPCWSTR) pEntry->GetPathNameUNI(), NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
break;
|
|
}
|
|
LOGD("Rename of '%ls' to '%ls' succeeded",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) renameDlg.fNewName);
|
|
} else if (result == IDCANCEL) {
|
|
LOGI("Canceling out of remaining renames");
|
|
break;
|
|
} else {
|
|
/* 3rd possibility is IDIGNORE, i.e. skip this entry */
|
|
LOGI("Skipping rename of '%ls'", (LPCWSTR) pEntry->GetDisplayName());
|
|
}
|
|
|
|
pSelEntry = pSelSet->IterNext();
|
|
}
|
|
|
|
/* flush pending rename calls */
|
|
{
|
|
CWaitCursor waitc;
|
|
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to rename all files: %hs.",
|
|
NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
}
|
|
}
|
|
|
|
/* reload GenericArchive from NufxLib */
|
|
if (InternalReload(fpMsgWnd) == kNuErrNone)
|
|
retVal = true;
|
|
|
|
return retVal;
|
|
}
|
|
|
|
CString NufxArchive::TestPathName(const GenericEntry* pGenericEntry,
|
|
const CString& basePath, const CString& newName, char newFssep) const
|
|
{
|
|
CString errMsg;
|
|
ASSERT(pGenericEntry != NULL);
|
|
|
|
ASSERT(basePath.IsEmpty());
|
|
|
|
/* can't start or end with fssep */
|
|
if (newName.Left(1) == newFssep || newName.Right(1) == newFssep) {
|
|
errMsg.Format(L"Names in NuFX archives may not start or end with a "
|
|
L"path separator character (%c).",
|
|
newFssep);
|
|
goto bail;
|
|
}
|
|
|
|
/* if it's a disk image, don't allow complex paths */
|
|
if (pGenericEntry->GetRecordKind() == GenericEntry::kRecordKindDisk) {
|
|
if (newName.Find(newFssep) != -1) {
|
|
errMsg.Format(L"Disk image names may not contain a path separator "
|
|
L"character (%c).",
|
|
newFssep);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test for case-sensitive name collisions. Each individual path
|
|
* component must be compared
|
|
*/
|
|
GenericEntry* pEntry;
|
|
pEntry = GetEntries();
|
|
while (pEntry != NULL) {
|
|
if (pEntry != pGenericEntry &&
|
|
ComparePaths(pEntry->GetPathNameUNI(), pEntry->GetFssep(),
|
|
newName, newFssep) == 0)
|
|
{
|
|
errMsg = L"An entry with that name already exists.";
|
|
}
|
|
|
|
pEntry = pEntry->GetNext();
|
|
}
|
|
|
|
bail:
|
|
return errMsg;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- recompress
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool NufxArchive::RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
|
|
const RecompressOptionsDialog* pRecompOpts)
|
|
{
|
|
/*
|
|
* We have to uncompress the files into memory and then recompress them.
|
|
* We don't want to flush after every file (too slow), but we can't wait
|
|
* until they're expanded (unbounded memory requirements). So we have
|
|
* to keep expanding until we reach a certain limit, then call flush to
|
|
* push the changes out.
|
|
*
|
|
* Since we're essentially making the changes in place (it's actually
|
|
* all getting routed through the temp file), we need to delete the thread
|
|
* and re-add it. This isn't quite as thorough as "launder", which
|
|
* actually reconstructs the entire record.
|
|
*/
|
|
const int kMaxSizeInMemory = 2 * 1024 * 1024; // 2MB
|
|
CString errMsg;
|
|
NuError nerr;
|
|
bool retVal = false;
|
|
|
|
/* set the compression type */
|
|
nerr = NuSetValue(fpArchive, kNuValueDataCompression,
|
|
pRecompOpts->fCompressionType + kNuCompressNone);
|
|
if (nerr != kNuErrNone) {
|
|
LOGI("GLITCH: unable to set compression type to %d",
|
|
pRecompOpts->fCompressionType);
|
|
/* keep going */
|
|
}
|
|
|
|
fProgressAsRecompress = true;
|
|
|
|
/*
|
|
* Loop over all items in the selection set. Because the selection
|
|
* set has one entry for each interesting thread, we don't need to
|
|
* pry the NuRecord open and play with it.
|
|
*
|
|
* We should only be here for data forks, resource forks, and disk
|
|
* images. Comments and filenames are not compressed, and so cannot
|
|
* be recompressed.
|
|
*/
|
|
SelectionEntry* pSelEntry = pSelSet->IterNext();
|
|
long sizeInMemory = 0;
|
|
bool result = true;
|
|
NufxEntry* pEntry = NULL;
|
|
for ( ; pSelEntry != NULL; pSelEntry = pSelSet->IterNext()) {
|
|
pEntry = (NufxEntry*) pSelEntry->GetEntry();
|
|
|
|
/*
|
|
* Compress each thread in turn.
|
|
*/
|
|
if (pEntry->GetHasDataFork()) {
|
|
result = RecompressThread(pEntry, GenericEntry::kDataThread,
|
|
pRecompOpts, &sizeInMemory, &errMsg);
|
|
if (!result)
|
|
break;
|
|
}
|
|
if (pEntry->GetHasRsrcFork()) {
|
|
result = RecompressThread(pEntry, GenericEntry::kRsrcThread,
|
|
pRecompOpts, &sizeInMemory, &errMsg);
|
|
if (!result)
|
|
break;
|
|
}
|
|
if (pEntry->GetHasDiskImage()) {
|
|
result = RecompressThread(pEntry, GenericEntry::kDiskImageThread,
|
|
pRecompOpts, &sizeInMemory, &errMsg);
|
|
if (!result)
|
|
break;
|
|
}
|
|
/* don't do anything with comments */
|
|
|
|
/* if we're sitting on too much, push it out */
|
|
if (sizeInMemory > kMaxSizeInMemory) {
|
|
/* flush anything pending */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Unable to recompress all files: %hs.",
|
|
NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
} else {
|
|
LOGI("Cancelled out of sub-flush/compress");
|
|
}
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
|
|
goto bail;
|
|
}
|
|
|
|
sizeInMemory = 0;
|
|
}
|
|
}
|
|
|
|
/* handle errors that threw us out of the while loop */
|
|
if (!result) {
|
|
ASSERT(pEntry != NULL);
|
|
CString dispStr;
|
|
dispStr.Format(L"Failed while recompressing '%ls': %ls.",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) errMsg);
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
|
|
/* flush anything pending */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Unable to recompress all files: %hs.",
|
|
NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
} else {
|
|
LOGI("Cancelled out of flush/compress");
|
|
}
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
} else {
|
|
retVal = true;
|
|
}
|
|
|
|
bail:
|
|
/* abort anything that didn't get flushed */
|
|
NuAbort(fpArchive);
|
|
/* reload to pick up changes */
|
|
(void) InternalReload(pMsgWnd);
|
|
|
|
fProgressAsRecompress = false;
|
|
return retVal;
|
|
}
|
|
|
|
bool NufxArchive::RecompressThread(NufxEntry* pEntry, int threadKind,
|
|
const RecompressOptionsDialog* pRecompOpts, long* pSizeInMemory,
|
|
CString* pErrMsg)
|
|
{
|
|
NuThread thread;
|
|
NuThreadID threadID;
|
|
NuError nerr;
|
|
NuDataSource* pSource = NULL;
|
|
CString subErrMsg;
|
|
bool retVal = false;
|
|
char* buf = NULL;
|
|
long len = 0;
|
|
|
|
LOGD(" Recompressing %ld '%ls'", pEntry->GetRecordIdx(),
|
|
(LPCWSTR) pEntry->GetDisplayName());
|
|
|
|
/* get a copy of the thread header */
|
|
pEntry->FindThreadInfo(threadKind, &thread, pErrMsg);
|
|
if (!pErrMsg->IsEmpty()) {
|
|
pErrMsg->Format(L"Unable to locate thread for %ls (type %d)",
|
|
(LPCWSTR) pEntry->GetDisplayName(), threadKind);
|
|
goto bail;
|
|
}
|
|
threadID = NuGetThreadID(&thread);
|
|
|
|
/* if it's already in the target format, skip it */
|
|
if (thread.thThreadFormat == pRecompOpts->fCompressionType) {
|
|
LOGI("Skipping (fmt=%d) '%ls'",
|
|
pRecompOpts->fCompressionType,
|
|
(LPCWSTR) pEntry->GetDisplayName());
|
|
return true;
|
|
}
|
|
|
|
/* extract the thread */
|
|
int result;
|
|
result = pEntry->ExtractThreadToBuffer(threadKind, &buf, &len, &subErrMsg);
|
|
if (result == IDCANCEL) {
|
|
LOGI("Cancelled during extract!");
|
|
ASSERT(buf == NULL);
|
|
goto bail; /* abort anything that was pending */
|
|
} else if (result != IDOK) {
|
|
pErrMsg->Format(L"Failed while extracting '%ls': %ls",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) subErrMsg);
|
|
goto bail;
|
|
}
|
|
*pSizeInMemory += len;
|
|
|
|
/* create a data source for it */
|
|
nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed,
|
|
0, (const uint8_t*) buf, 0, len, ArrayDeleteHandler,
|
|
&pSource);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"Unable to create NufxLib data source (len=%d).",
|
|
len);
|
|
goto bail;
|
|
}
|
|
buf = NULL; // data source owns it now
|
|
|
|
/* delete the existing thread */
|
|
//LOGI("+++ DELETE threadIdx=%d", thread.threadIdx);
|
|
nerr = NuDeleteThread(fpArchive, thread.threadIdx);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"Unable to delete thread %d: %hs",
|
|
pEntry->GetRecordIdx(), NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/* mark the new thread for addition */
|
|
//LOGI("+++ ADD threadID=0x%08lx", threadID);
|
|
nerr = NuAddThread(fpArchive, pEntry->GetRecordIdx(), threadID,
|
|
pSource, NULL);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"Unable to add thread type %d: %hs",
|
|
threadID, NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
pSource = NULL; // now owned by nufxlib
|
|
|
|
/* at this point, we just wait for the flush in the outer loop */
|
|
retVal = true;
|
|
|
|
bail:
|
|
NuFreeDataSource(pSource);
|
|
return retVal;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- transfer files to another archive
|
|
* ===========================================================================
|
|
*/
|
|
|
|
GenericArchive::XferStatus NufxArchive::XferSelection(CWnd* pMsgWnd,
|
|
SelectionSet* pSelSet, ActionProgressDialog* pActionProgress,
|
|
const XferFileOptions* pXferOpts)
|
|
{
|
|
/*
|
|
* We get one entry in the selection set per record.
|
|
*
|
|
* I think this now throws kXferCancelled whenever it's supposed to. Not
|
|
* 100% sure, but it looks good.
|
|
*/
|
|
LOGD("NufxArchive XferSelection!");
|
|
XferStatus retval = kXferFailed;
|
|
unsigned char* dataBuf = NULL;
|
|
unsigned char* rsrcBuf = NULL;
|
|
CString errMsg, dispMsg;
|
|
|
|
pXferOpts->fTarget->XferPrepare(pXferOpts);
|
|
|
|
SelectionEntry* pSelEntry = pSelSet->IterNext();
|
|
for ( ; pSelEntry != NULL; pSelEntry = pSelSet->IterNext()) {
|
|
long dataLen=-1, rsrcLen=-1;
|
|
NufxEntry* pEntry = (NufxEntry*) pSelEntry->GetEntry();
|
|
LocalFileDetails fileDetails;
|
|
CString errMsg;
|
|
|
|
ASSERT(dataBuf == NULL);
|
|
ASSERT(rsrcBuf == NULL);
|
|
|
|
/* in case we start handling CRC errors better */
|
|
if (pEntry->GetDamaged()) {
|
|
LOGI(" XFER skipping damaged entry '%ls'",
|
|
(LPCWSTR) pEntry->GetDisplayName());
|
|
continue;
|
|
}
|
|
|
|
LOGD(" XFER converting '%ls'", (LPCWSTR) pEntry->GetDisplayName());
|
|
|
|
// Generate a name that is the combination of the sub-volume name
|
|
// (if any) and the path name. This is likely the same as the
|
|
// "display name", but that is officially for display only, and
|
|
// shouldn't be used for this.
|
|
CString xferName;
|
|
const CString& subVolName = pEntry->GetSubVolName();
|
|
if (!subVolName.IsEmpty()) {
|
|
xferName = subVolName + (WCHAR) DiskFS::kDIFssep;
|
|
}
|
|
xferName += pEntry->GetPathNameUNI();
|
|
|
|
fileDetails.SetStrippedLocalPathName(xferName);
|
|
fileDetails.SetFssep(PathProposal::kDefaultStoredFssep);
|
|
fileDetails.SetFileSysFmt(DiskImg::kFormatUnknown);
|
|
fileDetails.SetFileType(pEntry->GetFileType());
|
|
fileDetails.SetExtraType(pEntry->GetAuxType());
|
|
fileDetails.SetAccess(pEntry->GetAccess());
|
|
fileDetails.SetStorageType(kNuStorageSeedling);
|
|
|
|
time_t when;
|
|
NuDateTime nuwhen;
|
|
when = time(NULL);
|
|
UNIXTimeToDateTime(&when, &nuwhen);
|
|
fileDetails.SetArchiveWhen(nuwhen);
|
|
when = pEntry->GetModWhen();
|
|
UNIXTimeToDateTime(&when, &nuwhen);
|
|
fileDetails.SetModWhen(nuwhen);
|
|
when = pEntry->GetCreateWhen();
|
|
UNIXTimeToDateTime(&when, &nuwhen);
|
|
fileDetails.SetCreateWhen(nuwhen);
|
|
|
|
pActionProgress->SetArcName(fileDetails.GetStrippedLocalPathName());
|
|
if (pActionProgress->SetProgress(0) == IDCANCEL) {
|
|
retval = kXferCancelled;
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Handle all relevant threads in this record. We assume it's either
|
|
* a data/rsrc pair or a disk image.
|
|
*/
|
|
if (pEntry->GetHasDataFork()) {
|
|
/*
|
|
* Found a data thread.
|
|
*/
|
|
int result;
|
|
dataBuf = NULL;
|
|
dataLen = 0;
|
|
result = pEntry->ExtractThreadToBuffer(GenericEntry::kDataThread,
|
|
(char**) &dataBuf, &dataLen, &errMsg);
|
|
if (result == IDCANCEL) {
|
|
LOGI("Cancelled during data extract!");
|
|
retval = kXferCancelled;
|
|
goto bail; /* abort anything that was pending */
|
|
} else if (result != IDOK) {
|
|
dispMsg.Format(L"Failed while extracting '%ls': %ls.",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) errMsg);
|
|
ShowFailureMsg(pMsgWnd, dispMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
ASSERT(dataBuf != NULL);
|
|
ASSERT(dataLen >= 0);
|
|
|
|
} else if (pEntry->GetHasDiskImage()) {
|
|
/*
|
|
* No data thread found. Look for a disk image.
|
|
*/
|
|
int result;
|
|
dataBuf = NULL;
|
|
dataLen = 0;
|
|
result = pEntry->ExtractThreadToBuffer(GenericEntry::kDiskImageThread,
|
|
(char**) &dataBuf, &dataLen, &errMsg);
|
|
if (result == IDCANCEL) {
|
|
LOGI("Cancelled during data extract!");
|
|
goto bail; /* abort anything that was pending */
|
|
} else if (result != IDOK) {
|
|
dispMsg.Format(L"Failed while extracting '%ls': %ls.",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) errMsg);
|
|
ShowFailureMsg(pMsgWnd, dispMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
ASSERT(dataBuf != NULL);
|
|
ASSERT(dataLen >= 0);
|
|
}
|
|
|
|
/*
|
|
* See if there's a resource fork in here (either by itself or
|
|
* with a data fork).
|
|
*/
|
|
if (pEntry->GetHasRsrcFork()) {
|
|
int result;
|
|
rsrcBuf = NULL;
|
|
rsrcLen = 0;
|
|
result = pEntry->ExtractThreadToBuffer(GenericEntry::kRsrcThread,
|
|
(char**) &rsrcBuf, &rsrcLen, &errMsg);
|
|
if (result == IDCANCEL) {
|
|
LOGI("Cancelled during rsrc extract!");
|
|
goto bail; /* abort anything that was pending */
|
|
} else if (result != IDOK) {
|
|
dispMsg.Format(L"Failed while extracting '%ls': %ls.",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) errMsg);
|
|
ShowFailureMsg(pMsgWnd, dispMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
fileDetails.SetStorageType(kNuStorageExtended);
|
|
} else {
|
|
ASSERT(rsrcBuf == NULL);
|
|
}
|
|
|
|
if (dataLen < 0 && rsrcLen < 0) {
|
|
LOGI(" XFER: WARNING: nothing worth transferring in '%ls'",
|
|
(LPCWSTR) pEntry->GetDisplayName());
|
|
continue;
|
|
}
|
|
|
|
errMsg = pXferOpts->fTarget->XferFile(&fileDetails, &dataBuf, dataLen,
|
|
&rsrcBuf, rsrcLen);
|
|
if (!errMsg.IsEmpty()) {
|
|
LOGI("XferFile failed!");
|
|
errMsg.Format(L"Failed while transferring '%ls': %ls.",
|
|
(LPCWSTR) pEntry->GetDisplayName(), (LPCWSTR) errMsg);
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
ASSERT(dataBuf == NULL);
|
|
ASSERT(rsrcBuf == NULL);
|
|
|
|
if (pActionProgress->SetProgress(100) == IDCANCEL) {
|
|
retval = kXferCancelled;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
retval = kXferOK;
|
|
|
|
bail:
|
|
if (retval != kXferOK)
|
|
pXferOpts->fTarget->XferAbort(pMsgWnd);
|
|
else
|
|
pXferOpts->fTarget->XferFinish(pMsgWnd);
|
|
delete[] dataBuf;
|
|
delete[] rsrcBuf;
|
|
return retval;
|
|
}
|
|
|
|
void NufxArchive::XferPrepare(const XferFileOptions* pXferOpts)
|
|
{
|
|
/*
|
|
* We set the "allow duplicates" flag because DOS 3.3 volumes can have
|
|
* files with duplicate names.
|
|
*/
|
|
LOGI(" NufxArchive::XferPrepare");
|
|
(void) NuSetValue(fpArchive, kNuValueAllowDuplicates, true);
|
|
}
|
|
|
|
CString NufxArchive::XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf,
|
|
long dataLen, uint8_t** pRsrcBuf, long rsrcLen)
|
|
{
|
|
NuError nerr;
|
|
const int kFileTypeTXT = 0x04;
|
|
NuDataSource* pSource = NULL;
|
|
CString errMsg;
|
|
|
|
LOGI(" NufxArchive::XferFile '%ls'",
|
|
(LPCWSTR) pDetails->GetStrippedLocalPathName());
|
|
LOGI(" dataBuf=0x%p dataLen=%ld rsrcBuf=0x%p rsrcLen=%ld",
|
|
*pDataBuf, dataLen, *pRsrcBuf, rsrcLen);
|
|
ASSERT(pDataBuf != NULL);
|
|
ASSERT(pRsrcBuf != NULL);
|
|
|
|
/* NuFX doesn't explicitly store directories */
|
|
if (pDetails->GetEntryKind() == LocalFileDetails::kFileKindDirectory) {
|
|
delete[] *pDataBuf;
|
|
delete[] *pRsrcBuf;
|
|
*pDataBuf = *pRsrcBuf = NULL;
|
|
goto bail;
|
|
}
|
|
|
|
ASSERT(dataLen >= 0 || rsrcLen >= 0);
|
|
ASSERT(*pDataBuf != NULL || *pRsrcBuf != NULL);
|
|
|
|
/*
|
|
* Odd bit of trivia: NufxLib refuses to accept an fssep of '\0'. It
|
|
* really wants to have one. Which is annoying, since files coming
|
|
* from DOS or Pascal don't have one. We therefore need to supply
|
|
* one, so we provide 0xff on the theory that nobody in their right
|
|
* mind would have it in an Apple II filename.
|
|
*
|
|
* Since we don't strip Pascal and ProDOS names down when we load
|
|
* the disks, it's possible the for 0xff to occur if the disk got
|
|
* damaged. For ProDOS we don't care, since it has an fssep, but
|
|
* Pascal could be at risk. DOS and RDOS are sanitized and so should
|
|
* be okay.
|
|
*
|
|
* One issue: we don't currently allow changing the fssep when renaming
|
|
* a file. We need to fix this, or else there's no way to rename a
|
|
* file into a subdirectory once it has been pasted in this way.
|
|
*
|
|
* TODO: fix this in NufxLib
|
|
*/
|
|
if (pDetails->GetFssep() == '\0') {
|
|
pDetails->SetFssep(kNufxNoFssep);
|
|
}
|
|
|
|
/* add the record; we have "allow duplicates" enabled for clashes */
|
|
NuRecordIdx recordIdx;
|
|
const NuFileDetails& nuFileDetails = pDetails->GetNuFileDetails();
|
|
|
|
nerr = NuAddRecord(fpArchive, &nuFileDetails, &recordIdx);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Failed adding record: %hs", NuStrError(nerr));
|
|
//ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
// else the add was cancelled
|
|
goto bail;
|
|
}
|
|
|
|
if (dataLen >= 0) {
|
|
ASSERT(*pDataBuf != NULL);
|
|
|
|
/* strip the high ASCII from DOS and RDOS text files */
|
|
if (pDetails->GetEntryKind() != LocalFileDetails::kFileKindDiskImage &&
|
|
pDetails->GetFileType() == kFileTypeTXT &&
|
|
DiskImg::UsesDOSFileStructure(pDetails->GetFileSysFmt()))
|
|
{
|
|
LOGI(" Stripping high ASCII from '%ls'",
|
|
(LPCWSTR) pDetails->GetStrippedLocalPathName());
|
|
uint8_t* ucp = *pDataBuf;
|
|
long len = dataLen;
|
|
|
|
while (len--)
|
|
*ucp++ &= 0x7f;
|
|
}
|
|
|
|
/* create a data source for the data fork; might be zero len */
|
|
nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, 0,
|
|
*pDataBuf, 0, dataLen, ArrayDeleteHandler, &pSource);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg = "Unable to create NufxLib data source.";
|
|
//ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
*pDataBuf = NULL; /* owned by data source */
|
|
|
|
/* add the data fork, as a disk image if appropriate */
|
|
NuThreadID targetID;
|
|
if (pDetails->GetEntryKind() == LocalFileDetails::kFileKindDiskImage)
|
|
targetID = kNuThreadIDDiskImage;
|
|
else
|
|
targetID = kNuThreadIDDataFork;
|
|
|
|
nerr = NuAddThread(fpArchive, recordIdx, targetID, pSource, NULL);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Failed adding thread: %hs.", NuStrError(nerr));
|
|
//ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
pSource = NULL; /* NufxLib owns it now */
|
|
}
|
|
|
|
/* add the resource fork, if one was provided */
|
|
if (rsrcLen >= 0) {
|
|
ASSERT(*pRsrcBuf != NULL);
|
|
|
|
nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, 0,
|
|
*pRsrcBuf, 0, rsrcLen, ArrayDeleteHandler, &pSource);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg = L"Unable to create NufxLib data source.";
|
|
//ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
*pRsrcBuf = NULL; /* owned by data source */
|
|
|
|
/* add the data fork */
|
|
nerr = NuAddThread(fpArchive, recordIdx, kNuThreadIDRsrcFork,
|
|
pSource, NULL);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Failed adding thread: %hs.", NuStrError(nerr));
|
|
//ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
pSource = NULL; /* NufxLib owns it now */
|
|
}
|
|
|
|
bail:
|
|
NuFreeDataSource(pSource);
|
|
return errMsg;
|
|
}
|
|
|
|
void NufxArchive::XferAbort(CWnd* pMsgWnd)
|
|
{
|
|
/*
|
|
* Since we don't do any interim flushes, we can just call NuAbort. If that
|
|
* weren't the case, we would need to delete all records and flush.
|
|
*/
|
|
NuError nerr;
|
|
CString errMsg;
|
|
|
|
LOGI(" NufxArchive::XferAbort");
|
|
|
|
nerr = NuAbort(fpArchive);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Failed while aborting procedure: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
}
|
|
|
|
void NufxArchive::XferFinish(CWnd* pMsgWnd)
|
|
{
|
|
NuError nerr;
|
|
CString errMsg;
|
|
|
|
LOGI(" NufxArchive::XferFinish");
|
|
|
|
/* actually do the work */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr != kNuErrAborted) {
|
|
errMsg.Format(L"Unable to add file: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(fpMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
goto bail;
|
|
}
|
|
|
|
(void) InternalReload(fpMsgWnd);
|
|
|
|
bail:
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* NufxArchive -- add/update/delete comments
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Extract a comment from the archive, converting line terminators to CRLF.
|
|
*
|
|
* Returns "true" on success, "false" on failure.
|
|
*/
|
|
bool NufxArchive::GetComment(CWnd* pMsgWnd, const GenericEntry* pGenericEntry,
|
|
CString* pStr)
|
|
{
|
|
NufxEntry* pEntry = (NufxEntry*) pGenericEntry;
|
|
CString errMsg;
|
|
const char* kNewEOL = "\r\n";
|
|
int result;
|
|
char* buf;
|
|
long len;
|
|
|
|
ASSERT(pGenericEntry->GetHasComment());
|
|
|
|
/* use standard extract function to pull comment out */
|
|
buf = NULL;
|
|
len = 0;
|
|
result = pEntry->ExtractThreadToBuffer(GenericEntry::kCommentThread,
|
|
&buf, &len, &errMsg);
|
|
if (result != IDOK) {
|
|
LOGI("Failed getting comment: %hs", buf);
|
|
ASSERT(buf == NULL);
|
|
return false;
|
|
}
|
|
|
|
/* convert EOL and add '\0' */
|
|
CString convStr;
|
|
const char* ccp;
|
|
|
|
ccp = buf;
|
|
while (len-- && *ccp != '\0') {
|
|
if (len > 1 && *ccp == '\r' && *(ccp+1) == '\n') {
|
|
ccp++;
|
|
len--;
|
|
convStr += kNewEOL;
|
|
} else if (*ccp == '\r' || *ccp == '\n') {
|
|
convStr += kNewEOL;
|
|
} else {
|
|
convStr += *ccp;
|
|
}
|
|
ccp++;
|
|
}
|
|
|
|
*pStr = convStr;
|
|
delete[] buf;
|
|
return true;
|
|
}
|
|
|
|
bool NufxArchive::SetComment(CWnd* pMsgWnd, GenericEntry* pGenericEntry,
|
|
const CString& str)
|
|
{
|
|
/*
|
|
* Set the comment. This requires either adding a new comment or updating
|
|
* an existing one. The latter is constrained by the maximum size of the
|
|
* comment buffer.
|
|
*
|
|
* We want to update in place whenever possible because it's faster (don't
|
|
* have to rewrite the entire archive), but that really only holds for new
|
|
* archives or if we foolishly set the kNuValueModifyOrig flag.
|
|
*
|
|
* Cleanest approach is to delete the existing thread and add a new one.
|
|
* If somebody complains we can try to be smarter about it.
|
|
*/
|
|
NuDataSource* pSource = NULL;
|
|
NufxEntry* pEntry = (NufxEntry*) pGenericEntry;
|
|
NuError nerr;
|
|
bool retVal = false;
|
|
|
|
/* convert CRLF to CR */
|
|
CStringA newStr(str);
|
|
char* srcp;
|
|
char* dstp;
|
|
srcp = dstp = newStr.GetBuffer(0);
|
|
while (*srcp != '\0') {
|
|
if (*srcp == '\r' && *(srcp+1) == '\n') {
|
|
srcp++;
|
|
*dstp = '\r';
|
|
} else {
|
|
*dstp = *srcp;
|
|
}
|
|
srcp++;
|
|
dstp++;
|
|
}
|
|
*dstp = '\0';
|
|
newStr.ReleaseBuffer();
|
|
|
|
/* get the thread info */
|
|
CString errMsg;
|
|
NuThread thread;
|
|
NuThreadIdx threadIdx;
|
|
|
|
pEntry->FindThreadInfo(GenericEntry::kCommentThread, &thread, &errMsg);
|
|
threadIdx = thread.threadIdx;
|
|
if (errMsg.IsEmpty()) {
|
|
/* delete existing thread */
|
|
nerr = NuDeleteThread(fpArchive, threadIdx);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to delete thread: %hs.", NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* set a maximum pre-size value for the thread */
|
|
long maxLen;
|
|
maxLen = ((newStr.GetLength() + 99) / 100) * 100;
|
|
if (maxLen < 200)
|
|
maxLen = 200;
|
|
|
|
|
|
/* create a data source to write from */
|
|
nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed,
|
|
maxLen, (const uint8_t*)(LPCSTR) newStr, 0,
|
|
newStr.GetLength(), NULL, &pSource);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to create NufxLib data source (len=%d, maxLen=%d).",
|
|
newStr.GetLength(), maxLen);
|
|
goto bail;
|
|
}
|
|
|
|
/* add the new thread */
|
|
nerr = NuAddThread(fpArchive, pEntry->GetRecordIdx(),
|
|
kNuThreadIDComment, pSource, NULL);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to add comment thread: %hs.",
|
|
NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
pSource = NULL; // nufxlib owns it now
|
|
|
|
/* flush changes */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to flush comment changes: %hs.",
|
|
NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/* reload GenericArchive from NufxLib */
|
|
if (InternalReload(fpMsgWnd) == kNuErrNone)
|
|
retVal = true;
|
|
|
|
bail:
|
|
NuFreeDataSource(pSource);
|
|
if (!retVal) {
|
|
LOGI("FAILED: %ls", (LPCWSTR) errMsg);
|
|
NuAbort(fpArchive);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
bool NufxArchive::DeleteComment(CWnd* pMsgWnd, GenericEntry* pGenericEntry)
|
|
{
|
|
CString errMsg;
|
|
NuError nerr;
|
|
NufxEntry* pEntry = (NufxEntry*) pGenericEntry;
|
|
NuThread thread;
|
|
NuThreadIdx threadIdx;
|
|
bool retVal = false;
|
|
|
|
pEntry->FindThreadInfo(GenericEntry::kCommentThread, &thread, &errMsg);
|
|
if (!errMsg.IsEmpty())
|
|
goto bail;
|
|
threadIdx = thread.threadIdx;
|
|
|
|
nerr = NuDeleteThread(fpArchive, threadIdx);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to delete thread: %hs.", NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/* flush changes */
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unable to flush comment deletion: %hs.",
|
|
NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
/* reload GenericArchive from NufxLib */
|
|
if (InternalReload(pMsgWnd) == kNuErrNone)
|
|
retVal = true;
|
|
|
|
bail:
|
|
if (retVal != 0) {
|
|
LOGI("FAILED: %ls", (LPCWSTR) errMsg);
|
|
NuAbort(fpArchive);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
bool NufxArchive::SetProps(CWnd* pMsgWnd, GenericEntry* pEntry,
|
|
const FileProps* pProps)
|
|
{
|
|
/*
|
|
* Sets file properties via the NuSetRecordAttr call.
|
|
*
|
|
* Gets the existing properties, copies the fields from FileProps over, and
|
|
* sets them. [currently only supports file type, aux type, and access flags]
|
|
*
|
|
* Technically we should reload the GenericArchive from the NufxArchive,
|
|
* but the set of changes is pretty small, so we just make them here.
|
|
*/
|
|
NuError nerr;
|
|
NufxEntry* pNufxEntry = (NufxEntry*) pEntry;
|
|
const NuRecord* pRecord;
|
|
NuRecordAttr recordAttr;
|
|
|
|
LOGI(" SET fileType=0x%02x auxType=0x%04x access=0x%02x",
|
|
pProps->fileType, pProps->auxType, pProps->access);
|
|
|
|
nerr = NuGetRecord(fpArchive, pNufxEntry->GetRecordIdx(), &pRecord);
|
|
if (nerr != kNuErrNone) {
|
|
LOGI("ERROR: couldn't find recordIdx %ld: %hs",
|
|
pNufxEntry->GetRecordIdx(), NuStrError(nerr));
|
|
return false;
|
|
}
|
|
|
|
NuRecordCopyAttr(&recordAttr, pRecord);
|
|
recordAttr.fileType = pProps->fileType;
|
|
recordAttr.extraType = pProps->auxType;
|
|
recordAttr.access = pProps->access;
|
|
|
|
nerr = NuSetRecordAttr(fpArchive, pNufxEntry->GetRecordIdx(), &recordAttr);
|
|
if (nerr != kNuErrNone) {
|
|
LOGI("ERROR: couldn't set recordAttr %ld: %hs",
|
|
pNufxEntry->GetRecordIdx(), NuStrError(nerr));
|
|
return false;
|
|
}
|
|
|
|
uint32_t statusFlags;
|
|
nerr = NuFlush(fpArchive, &statusFlags);
|
|
if (nerr != kNuErrNone) {
|
|
LOGI("ERROR: NuFlush failed: %hs", NuStrError(nerr));
|
|
|
|
/* see if it got converted to read-only status */
|
|
if (statusFlags & kNuFlushReadOnly)
|
|
fIsReadOnly = true;
|
|
return false;
|
|
}
|
|
|
|
LOGI("Props set");
|
|
|
|
/* do this in lieu of reloading GenericArchive */
|
|
pEntry->SetFileType(pProps->fileType);
|
|
pEntry->SetAuxType(pProps->auxType);
|
|
pEntry->SetAccess(pProps->access);
|
|
|
|
return true;
|
|
}
|