/* * 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, " %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; }