ciderpress/app/NufxArchive.cpp
Andy McFadden 00e6b3ab5e Improve filename handling when reading from archives
This updates GenericEntry's filename handling to be more careful
about Mac OS Roman vs. Unicode.  Most of the work is still done with
a CP-1252 conversion instead of MOR, but we now do a proper
conversion on the "display name", so we see the right thing in the
content list and file viewer.

Copy & paste, disk-to-file-archive, and file-archive-to-disk
conversions should work (correctly) as before.  Extracted files will
still have "_" or "%AA" instead of a Unicode TRADE MARK SIGN, but
that's fine for now -- we can extract and re-add the files losslessly.

The filenames are now stored in CStrings rather than WCHAR*.

Also, fixed a bad initializer in the file-archive-to-disk conversion
dialog.
2015-01-08 17:57:20 -08:00

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 {
LOGI("WARNING: ignoring second disk image / data fork");
}
}
if (threadID == kNuThreadIDRsrcFork) {
if (!GetHasRsrcFork()) {
SetHasRsrcFork(true);
SetRsrcForkLen(pThread->actualThreadEOF);
} else {
LOGI("WARNING: ignoring second data fork");
}
}
if (threadID == kNuThreadIDDiskImage) {
if (!GetHasDiskImage() && !GetHasDataFork()) {
SetHasDiskImage(true);
SetDataForkLen(pThread->actualThreadEOF);
} else {
LOGI("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, origNameA;
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();
origNameA = fileName; // narrowing conversion
storageNameA = pathProp.fStoredPathName;
details.origName = origNameA;
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);
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 = "???";
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;
}