ciderpress/app/NufxArchive.cpp

2505 lines
80 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"
#include "../reformat/Charset.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);
// TODO(Unicode): NufxLib should be handing us these in UTF-16
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);
CString oldNameW(oldName);
CString newNameW(newName);
status = SET_PROGRESS_UPDATE2(perc,
oldNameW.IsEmpty() ? NULL : (LPCWSTR) oldNameW,
newNameW.IsEmpty() ? NULL : (LPCWSTR) 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)
{
// not invalid; just no date set
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;
}
// Must range-check values before passing them to CTime constructor, which
// now throws a remarkably fatal exception.
if (pDateTime->month > 11 || // [0,11]
pDateTime->day > 30 || // [0,30]
pDateTime->hour > 59 || // [0,59]
pDateTime->minute > 59 || // [0,59]
pDateTime->second > 59) { // [0,59]
return kDateInvalid;
}
CTime modTime(year,
pDateTime->month+1,
pDateTime->day+1,
pDateTime->hour,
pDateTime->minute,
pDateTime->second);
time_t result = (time_t)modTime.GetTime();
return result;
}
/*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);
confOvwr.fExistingFile = Charset::ConvertMORToUNI(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;
}