From b79498da50cbd136eb5d049bbf2236b366b21e90 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sun, 4 Jan 2015 21:04:01 -0800 Subject: [PATCH] Improve filename handling when adding files Most of this change is a conversion of the old FileDetails struct into a new LocalFileDetails class. The new class keeps the members private, and keeps the Unicode and MOR representations of the string separate. The NuFX and DiskImg libraries don't support UTF-16 filenames, so we stil can't add files with non-CP-1252 filenames, but we're a step closer. Also, update NufxLib with a couple of fixes from the main project. Also, fix handling of "%00" when adding files. Also, mark most of the A2FileDOS fields private. Not sure why they weren't. --- app/ACUArchive.cpp | 6 +- app/ACUArchive.h | 4 +- app/Actions.cpp | 30 +-- app/BNYArchive.h | 4 +- app/BasicImport.cpp | 26 +-- app/CassetteDialog.cpp | 36 ++-- app/Clipboard.cpp | 80 ++++---- app/DiskArchive.cpp | 242 +++++++++++------------ app/DiskArchive.h | 37 ++-- app/FileNameConv.cpp | 6 +- app/GenericArchive.cpp | 427 +++++++++++++++++++--------------------- app/GenericArchive.h | 252 ++++++++++++++++-------- app/Main.cpp | 24 +-- app/Main.h | 4 +- app/NufxArchive.cpp | 150 +++++++------- app/NufxArchive.h | 4 +- diskimg/DiskImg.cpp | 62 +++--- diskimg/DiskImg.h | 26 ++- diskimg/DiskImgDetail.h | 11 +- diskimg/GenericFD.cpp | 13 +- nufxlib/FileIO.c | 7 +- nufxlib/NufxLibPriv.h | 28 +-- util/MyDebug.cpp | 18 +- util/PathName.h | 24 +++ 24 files changed, 824 insertions(+), 697 deletions(-) diff --git a/app/ACUArchive.cpp b/app/ACUArchive.cpp index e0943be..37ec012 100644 --- a/app/ACUArchive.cpp +++ b/app/ACUArchive.cpp @@ -375,7 +375,7 @@ bail: /*static*/ CString AcuArchive::AppInit(void) { - return ""; + return L""; } GenericArchive::OpenResult AcuArchive::Open(const WCHAR* filename, @@ -526,11 +526,11 @@ int AcuArchive::ReadMasterHeader(int* pNumEntries) header.unknown1 != 1 || strcmp((char*) header.fZink, "fZink") != 0) { - LOGI("Not an ACU archive"); + LOGW("Not an ACU archive"); return -1; } - LOGI("Looks like an ACU archive with %d entries", header.fileCount); + LOGD("Looks like an ACU archive with %d entries", header.fileCount); *pNumEntries = header.fileCount; return 0; diff --git a/app/ACUArchive.h b/app/ACUArchive.h index 1c41b9f..55ed20d 100644 --- a/app/ACUArchive.h +++ b/app/ACUArchive.h @@ -162,7 +162,7 @@ private: } virtual void XferPrepare(const XferFileOptions* pXferOpts) override { ASSERT(false); } - virtual CString XferFile(FileDetails* pDetails, uint8_t** pDataBuf, + virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf, long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override { ASSERT(false); return "!"; } virtual void XferAbort(CWnd* pMsgWnd) override @@ -172,7 +172,7 @@ private: virtual ArchiveKind GetArchiveKind(void) override { return kArchiveACU; } virtual NuError DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) override + LocalFileDetails* pDetails) override { ASSERT(false); return kNuErrGeneric; } enum { diff --git a/app/Actions.cpp b/app/Actions.cpp index 71b725a..c1835cb 100644 --- a/app/Actions.cpp +++ b/app/Actions.cpp @@ -2138,21 +2138,21 @@ void MainWindow::OnUpdateActionsConvFromWav(CCmdUI* pCmdUI) pCmdUI->Enable(fpContentList != NULL && !fpOpenArchive->IsReadOnly()); } -/*static*/ bool MainWindow::SaveToArchive(GenericArchive::FileDetails* pDetails, - const unsigned char* dataBufIn, long dataLen, - const unsigned char* rsrcBufIn, long rsrcLen, - CString& errMsg, CWnd* pDialog) +/*static*/ bool MainWindow::SaveToArchive(GenericArchive::LocalFileDetails* pDetails, + const uint8_t* dataBufIn, long dataLen, + const uint8_t* rsrcBufIn, long rsrcLen, + CString* pErrMsg, CWnd* pDialog) { MainWindow* pMain = GET_MAIN_WINDOW(); GenericArchive* pArchive = pMain->GetOpenArchive(); DiskImgLib::A2File* pTargetSubdir = NULL; XferFileOptions xferOpts; CString storagePrefix; - unsigned char* dataBuf = NULL; - unsigned char* rsrcBuf = NULL; + uint8_t* dataBuf = NULL; + uint8_t* rsrcBuf = NULL; ASSERT(pArchive != NULL); - ASSERT(errMsg.IsEmpty()); + ASSERT(pErrMsg->IsEmpty()); /* * Make a copy of the data for XferFile. @@ -2163,7 +2163,7 @@ void MainWindow::OnUpdateActionsConvFromWav(CCmdUI* pCmdUI) else dataBuf = new unsigned char[dataLen]; if (dataBuf == NULL) { - errMsg.Format(L"Unable to allocate %ld bytes", dataLen); + pErrMsg->Format(L"Unable to allocate %ld bytes", dataLen); goto bail; } memcpy(dataBuf, dataBufIn, dataLen); @@ -2187,7 +2187,7 @@ void MainWindow::OnUpdateActionsConvFromWav(CCmdUI* pCmdUI) // Always use ':' separator for SHK; this is a matter of // convenience, so they can specify a full path. //details.storageName.Replace(':', '_'); - pDetails->fileSysInfo = ':'; + pDetails->SetFssep(':'); } if (pTargetSubdir != NULL) { storagePrefix = pTargetSubdir->GetPathName(); @@ -2195,13 +2195,13 @@ void MainWindow::OnUpdateActionsConvFromWav(CCmdUI* pCmdUI) } if (!storagePrefix.IsEmpty()) { CString tmpStr, tmpFileName; - tmpFileName = pDetails->storageName; + tmpFileName = pDetails->GetStrippedLocalPathName(); tmpFileName.Replace(':', '_'); // strip any ':'s in the name - pDetails->fileSysInfo = ':'; + pDetails->SetFssep(':'); tmpStr = storagePrefix; tmpStr += ':'; tmpStr += tmpFileName; - pDetails->storageName = tmpStr; + pDetails->SetStrippedLocalPathName(tmpStr); } /* @@ -2211,18 +2211,18 @@ void MainWindow::OnUpdateActionsConvFromWav(CCmdUI* pCmdUI) */ pArchive->XferPrepare(&xferOpts); - errMsg = pArchive->XferFile(pDetails, &dataBuf, dataLen, + *pErrMsg = pArchive->XferFile(pDetails, &dataBuf, dataLen, &rsrcBuf, rsrcLen); delete[] dataBuf; delete[] rsrcBuf; - if (errMsg.IsEmpty()) + if (pErrMsg->IsEmpty()) pArchive->XferFinish(pDialog); else pArchive->XferAbort(pDialog); bail: - return (errMsg.IsEmpty() != 0); + return (pErrMsg->IsEmpty() != 0); } diff --git a/app/BNYArchive.h b/app/BNYArchive.h index c4cbedd..e1e9095 100644 --- a/app/BNYArchive.h +++ b/app/BNYArchive.h @@ -149,7 +149,7 @@ private: } virtual void XferPrepare(const XferFileOptions* pXferOpts) override { ASSERT(false); } - virtual CString XferFile(FileDetails* pDetails, uint8_t** pDataBuf, + virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf, long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override { ASSERT(false); return "!"; } virtual void XferAbort(CWnd* pMsgWnd) override @@ -159,7 +159,7 @@ private: virtual ArchiveKind GetArchiveKind(void) override { return kArchiveBNY; } virtual NuError DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) override + LocalFileDetails* pDetails) override { ASSERT(false); return kNuErrGeneric; } enum { diff --git a/app/BasicImport.cpp b/app/BasicImport.cpp index 5521c24..166a7f2 100644 --- a/app/BasicImport.cpp +++ b/app/BasicImport.cpp @@ -616,25 +616,27 @@ void ImportBASDialog::OnOK(void) /* * Write the file to the currently-open archive. */ - GenericArchive::FileDetails details; + GenericArchive::LocalFileDetails details; - details.entryKind = GenericArchive::FileDetails::kFileKindDataFork; - details.origName = L"Imported BASIC"; - details.storageName = fileName; - details.access = 0xe3; // unlocked, backup bit set - details.fileType = kFileTypeBAS; - details.extraType = 0x0801; - details.storageType = DiskFS::kStorageSeedling; + details.SetEntryKind(GenericArchive::LocalFileDetails::kFileKindDataFork); + details.SetLocalPathName(L"Imported BASIC"); + details.SetStrippedLocalPathName(fileName); + details.SetAccess(0xe3); // unlocked, backup bit set + details.SetFileType(kFileTypeBAS); + details.SetExtraType(0x0801); + details.SetStorageType(DiskFS::kStorageSeedling); time_t now = time(NULL); - GenericArchive::UNIXTimeToDateTime(&now, &details.createWhen); - GenericArchive::UNIXTimeToDateTime(&now, &details.archiveWhen); - GenericArchive::UNIXTimeToDateTime(&now, &details.modWhen); + NuDateTime ndt; + GenericArchive::UNIXTimeToDateTime(&now, &ndt); + details.SetCreateWhen(ndt); + details.SetArchiveWhen(ndt); + details.SetModWhen(ndt); CString errMsg; fDirty = true; if (!MainWindow::SaveToArchive(&details, (const unsigned char*) fOutput, - fOutputLen, NULL, -1, /*ref*/errMsg, this)) + fOutputLen, NULL, -1, &errMsg, this)) { goto bail; } diff --git a/app/CassetteDialog.cpp b/app/CassetteDialog.cpp index 39540aa..c5fc52a 100644 --- a/app/CassetteDialog.cpp +++ b/app/CassetteDialog.cpp @@ -418,29 +418,33 @@ void CassetteDialog::OnImport(void) /* * Write the file to the currently-open archive. */ - GenericArchive::FileDetails details; + GenericArchive::LocalFileDetails details; - details.entryKind = GenericArchive::FileDetails::kFileKindDataFork; - details.origName = "Cassette WAV"; - details.storageName = impDialog.fFileName; - details.access = 0xe3; // unlocked, backup bit set - details.fileType = impDialog.GetFileType(); - if (details.fileType == kFileTypeBIN) - details.extraType = impDialog.fStartAddr; - else if (details.fileType == kFileTypeBAS) - details.extraType = 0x0801; - else - details.extraType = 0x0000; - details.storageType = DiskFS::kStorageSeedling; + details.SetEntryKind(GenericArchive::LocalFileDetails::kFileKindDataFork); + details.SetLocalPathName(L"Cassette WAV"); + details.SetStrippedLocalPathName(impDialog.fFileName); + details.SetAccess(0xe3); // unlocked, backup bit set + details.SetFileType(impDialog.GetFileType()); + if (details.GetFileType() == kFileTypeBIN) { + details.SetExtraType(impDialog.fStartAddr); + } else if (details.GetFileType() == kFileTypeBAS) { + details.SetExtraType(0x0801); + } else { + details.SetExtraType(0x0000); + } + details.SetStorageType(DiskFS::kStorageSeedling); time_t now = time(NULL); - GenericArchive::UNIXTimeToDateTime(&now, &details.createWhen); - GenericArchive::UNIXTimeToDateTime(&now, &details.archiveWhen); + NuDateTime ndt; + GenericArchive::UNIXTimeToDateTime(&now, &ndt); + details.SetCreateWhen(ndt); + details.SetArchiveWhen(ndt); + details.SetModWhen(ndt); CString errMsg; fDirty = true; if (!MainWindow::SaveToArchive(&details, fDataArray[idx].GetDataBuf(), - fDataArray[idx].GetDataLen(), NULL, -1, /*ref*/errMsg, this)) + fDataArray[idx].GetDataLen(), NULL, -1, &errMsg, this)) { goto bail; } diff --git a/app/Clipboard.cpp b/app/Clipboard.cpp index c5ccb07..3d49358 100644 --- a/app/Clipboard.cpp +++ b/app/Clipboard.cpp @@ -80,11 +80,11 @@ typedef struct FileCollectionEntry { uint32_t cmmtLen; // len of comments uint32_t fileType; uint32_t auxType; - int64_t createWhen; // time_t - int64_t modWhen; // time_t + int64_t createWhen; // holds time_t + int64_t modWhen; // holds time_t uint8_t access; // ProDOS access flags uint8_t entryKind; // GenericArchive::FileDetails::FileKind - uint8_t sourceFS; // DiskImgLib::DiskImg::FSFormat + uint8_t sourceFS; // holds DiskImgLib::DiskImg::FSFormat uint8_t fssep; // filesystem separator char, e.g. ':' /* data comes next: null-terminated WCHAR filename, then data fork, then @@ -440,17 +440,17 @@ CString MainWindow::CopyToCollection(GenericEntry* pEntry, void** pBuf, return errStr; } - GenericArchive::FileDetails::FileKind entryKind; + GenericArchive::LocalFileDetails::FileKind entryKind; if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory) - entryKind = GenericArchive::FileDetails::kFileKindDirectory; + entryKind = GenericArchive::LocalFileDetails::kFileKindDirectory; else if (pEntry->GetHasDataFork() && pEntry->GetHasRsrcFork()) - entryKind = GenericArchive::FileDetails::kFileKindBothForks; + entryKind = GenericArchive::LocalFileDetails::kFileKindBothForks; else if (pEntry->GetHasDataFork()) - entryKind = GenericArchive::FileDetails::kFileKindDataFork; + entryKind = GenericArchive::LocalFileDetails::kFileKindDataFork; else if (pEntry->GetHasRsrcFork()) - entryKind = GenericArchive::FileDetails::kFileKindRsrcFork; + entryKind = GenericArchive::LocalFileDetails::kFileKindRsrcFork; else if (pEntry->GetHasDiskImage()) - entryKind = GenericArchive::FileDetails::kFileKindDiskImage; + entryKind = GenericArchive::LocalFileDetails::kFileKindDiskImage; else { ASSERT(false); return errStr; @@ -912,30 +912,32 @@ bail: CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt, const WCHAR* pathName, const uint8_t* buf, long remLen) { - GenericArchive::FileDetails::FileKind entryKind; - GenericArchive::FileDetails details; + GenericArchive::LocalFileDetails::FileKind entryKind; + GenericArchive::LocalFileDetails details; uint8_t* dataBuf = NULL; uint8_t* rsrcBuf = NULL; long dataLen, rsrcLen, cmmtLen; CString errMsg; - entryKind = (GenericArchive::FileDetails::FileKind) pCollEnt->entryKind; - LOGI(" Processing '%ls' (%d)", pathName, entryKind); + entryKind = (GenericArchive::LocalFileDetails::FileKind) pCollEnt->entryKind; + LOGD(" Processing '%ls' (%d)", pathName, entryKind); - details.entryKind = entryKind; - details.origName = L"Clipboard"; - details.storageName = pathName; // TODO MacRoman convert - details.fileSysFmt = (DiskImg::FSFormat) pCollEnt->sourceFS; - details.fileSysInfo = pCollEnt->fssep; - details.access = pCollEnt->access; - details.fileType = pCollEnt->fileType; - details.extraType = pCollEnt->auxType; - GenericArchive::UNIXTimeToDateTime(&pCollEnt->createWhen, - &details.createWhen); - GenericArchive::UNIXTimeToDateTime(&pCollEnt->modWhen, - &details.modWhen); + details.SetEntryKind(entryKind); + details.SetLocalPathName(L"Clipboard"); + details.SetStrippedLocalPathName(pathName); + details.SetFileSysFmt((DiskImg::FSFormat) pCollEnt->sourceFS); + details.SetFssep(pCollEnt->fssep); + details.SetAccess(pCollEnt->access); + details.SetFileType(pCollEnt->fileType); + details.SetExtraType(pCollEnt->auxType); + NuDateTime ndt; + GenericArchive::UNIXTimeToDateTime(&pCollEnt->createWhen, &ndt); + details.SetCreateWhen(ndt); + GenericArchive::UNIXTimeToDateTime(&pCollEnt->modWhen, &ndt); + details.SetModWhen(ndt); time_t now = time(NULL); - GenericArchive::UNIXTimeToDateTime(&now, &details.archiveWhen); + GenericArchive::UNIXTimeToDateTime(&now, &ndt); + details.SetArchiveWhen(ndt); /* * Because of the way XferFile works, we need to make a copy of @@ -958,23 +960,23 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt, */ bool hasData = false; bool hasRsrc = false; - if (details.entryKind == GenericArchive::FileDetails::kFileKindDataFork) { + if (entryKind == GenericArchive::LocalFileDetails::kFileKindDataFork) { hasData = true; - details.storageType = kNuStorageSeedling; - } else if (details.entryKind == GenericArchive::FileDetails::kFileKindRsrcFork) { + details.SetStorageType(kNuStorageSeedling); + } else if (entryKind == GenericArchive::LocalFileDetails::kFileKindRsrcFork) { hasRsrc = true; - details.storageType = kNuStorageExtended; - } else if (details.entryKind == GenericArchive::FileDetails::kFileKindBothForks) { + details.SetStorageType(kNuStorageExtended); + } else if (entryKind == GenericArchive::LocalFileDetails::kFileKindBothForks) { hasData = hasRsrc = true; - details.storageType = kNuStorageExtended; - } else if (details.entryKind == GenericArchive::FileDetails::kFileKindDiskImage) { + details.SetStorageType(kNuStorageExtended); + } else if (entryKind == GenericArchive::LocalFileDetails::kFileKindDiskImage) { hasData = true; - details.storageType = kNuStorageSeedling; - } else if (details.entryKind == GenericArchive::FileDetails::kFileKindDirectory) { - details.storageType = kNuStorageDirectory; + details.SetStorageType(kNuStorageSeedling); + } else if (entryKind == GenericArchive::LocalFileDetails::kFileKindDirectory) { + details.SetStorageType(kNuStorageDirectory); } else { ASSERT(false); - return "Internal error."; + return L"Internal error."; } if (hasData) { @@ -985,7 +987,7 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt, dataLen = pCollEnt->dataLen; dataBuf = new uint8_t[dataLen]; if (dataBuf == NULL) - return "memory allocation failed."; + return L"memory allocation failed."; memcpy(dataBuf, buf, dataLen); buf += dataLen; remLen -= dataLen; @@ -1003,7 +1005,7 @@ CString MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt, rsrcLen = pCollEnt->rsrcLen; rsrcBuf = new uint8_t[rsrcLen]; if (rsrcBuf == NULL) - return "Memory allocation failed."; + return L"Memory allocation failed."; memcpy(rsrcBuf, buf, rsrcLen); buf += rsrcLen; remLen -= rsrcLen; diff --git a/app/DiskArchive.cpp b/app/DiskArchive.cpp index 7e435af..e940153 100644 --- a/app/DiskArchive.cpp +++ b/app/DiskArchive.cpp @@ -466,21 +466,26 @@ GenericArchive::OpenResult DiskArchive::Open(const WCHAR* filename, { CWaitCursor waitc; + // TODO(Unicode): modify DiskImg lib to accept wide paths CStringA fileNameA(filename); + if (!PathName::TestNarrowConversion(filename, fileNameA, &errMsg)) { + result = kResultFailure; + goto bail; + } dierr = fDiskImg.OpenImage(fileNameA, PathProposal::kLocalFssep, readOnly); if (dierr == kDIErrAccessDenied && !readOnly && !isVolume) { // retry file open with read-only set // don't do that for volumes -- assume they know what they want - LOGI(" Retrying open with read-only set"); + LOGD(" Retrying open with read-only set"); fIsReadOnly = readOnly = true; dierr = fDiskImg.OpenImage(fileNameA, PathProposal::kLocalFssep, readOnly); } if (dierr != kDIErrNone) { - if (dierr == kDIErrFileArchive) + if (dierr == kDIErrFileArchive) { result = kResultFileArchive; - else { + } else { result = kResultFailure; errMsg.Format(L"Unable to open '%ls': %hs.", filename, DiskImgLib::DIStrError(dierr)); @@ -507,7 +512,7 @@ GenericArchive::OpenResult DiskArchive::Open(const WCHAR* filename, imf.SetAllowGenericFormats(false); if (imf.DoModal() != IDOK) { - LOGI("User bailed on IMF dialog"); + LOGD("User bailed on IMF dialog"); result = kResultCancel; goto bail; } @@ -1246,7 +1251,7 @@ bail: } NuError DiskArchive::DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) + LocalFileDetails* pDetails) { /* * Add a file to a disk image Unfortunately we can't just add the files @@ -1286,8 +1291,9 @@ NuError DiskArchive::DoAddFile(const AddFilesDialog* pAddOpts, int neededLen = 64; // reasonable guess char* fsNormalBuf = NULL; // name as it will appear on disk image - LOGI(" +++ ADD file: orig='%ls' stor='%ls'", - (LPCWSTR) pDetails->origName, (LPCWSTR) pDetails->storageName); + LOGI(" +++ ADD file: orig='%ls' strip='%ls'", + (LPCWSTR) pDetails->GetLocalPathName(), + (LPCWSTR) pDetails->GetStrippedLocalPathName()); retry: /* @@ -1295,14 +1301,13 @@ retry: */ delete[] fsNormalBuf; fsNormalBuf = new char[neededLen]; - CStringA storageNameA(pDetails->storageName); - dierr = pDiskFS->NormalizePath(storageNameA, + dierr = pDiskFS->NormalizePath(pDetails->GetStoragePathNameMOR(), PathProposal::kDefaultStoredFssep, fsNormalBuf, &neededLen); if (dierr == kDIErrDataOverrun) { /* not long enough, try again *once* */ delete[] fsNormalBuf; fsNormalBuf = new char[neededLen]; - dierr = pDiskFS->NormalizePath(storageNameA, + dierr = pDiskFS->NormalizePath(pDetails->GetStoragePathNameMOR(), PathProposal::kDefaultStoredFssep, fsNormalBuf, &neededLen); } if (dierr != kDIErrNone) { @@ -1337,16 +1342,16 @@ retry: goto retry; } else if (result == kNuOverwrite) { /* delete the existing file immediately */ - LOGI(" Deleting existing file '%hs'", fsNormalBuf); + LOGD(" Deleting existing file '%hs'", fsNormalBuf); dierr = pDiskFS->DeleteFile(pExisting); if (dierr != kDIErrNone) { // Would be nice to show a dialog and explain *why*, but // I'm not sure we have a window here. - LOGI(" Deletion failed (err=%d)", dierr); + LOGE(" Deletion failed (err=%d)", dierr); goto bail; } } else { - LOGI("GLITCH: bad return %d from HandleReplaceExisting",result); + LOGE("GLITCH: bad return %d from HandleReplaceExisting",result); assert(false); nuerr = kNuErrInternal; goto bail; @@ -1364,7 +1369,7 @@ retry: goto bail; } - LOGI("FSNormalized is '%hs'", pAddData->GetFSNormalPath()); + LOGD("FSNormalized is '%hs'", pAddData->GetFSNormalPath()); AddToAddDataList(pAddData); @@ -1374,7 +1379,7 @@ bail: } NuResult DiskArchive::HandleReplaceExisting(const A2File* pExisting, - FileDetails* pDetails) + LocalFileDetails* pDetails) { NuResult result; @@ -1390,8 +1395,8 @@ NuResult DiskArchive::HandleReplaceExisting(const A2File* pExisting, confOvwr.fExistingFile = pExisting->GetPathName(); confOvwr.fExistingFileModWhen = pExisting->GetModWhen(); - PathName srcPath(pDetails->origName); - confOvwr.fNewFileSource = pDetails->origName; // or storageName? + PathName srcPath(pDetails->GetLocalPathName()); + confOvwr.fNewFileSource = pDetails->GetLocalPathName(); confOvwr.fNewFileModWhen = srcPath.GetModWhen(); if (confOvwr.DoModal() == IDCANCEL) { @@ -1399,6 +1404,9 @@ NuResult DiskArchive::HandleReplaceExisting(const A2File* pExisting, return kNuAbort; } + // TODO: if they rename one fork, we need to track that fact and + // carry it over to the other fork -- otherwise they can rename + // the data and resource forks into separate files. if (confOvwr.fResultRename) { /* * Replace the name in FileDetails. They were asked to modify @@ -1413,8 +1421,9 @@ NuResult DiskArchive::HandleReplaceExisting(const A2File* pExisting, * full path and reject "OK" if it's not valid. Instead, we just * allow the FS normalizer to force the filename to be valid. */ - pDetails->storageName = confOvwr.fExistingFile; - LOGI("Trying rename to '%ls'", (LPCWSTR) pDetails->storageName); + pDetails->SetStrippedLocalPathName(confOvwr.fExistingFile); + LOGI("Trying rename to '%ls'", + (LPCWSTR) pDetails->GetStrippedLocalPathName()); return kNuRename; } @@ -1470,26 +1479,26 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) pData = fpAddDataHead; while (pData != NULL) { - const FileDetails* pDataDetails = NULL; - const FileDetails* pRsrcDetails = NULL; - const FileDetails* pDetails = pData->GetDetails(); + const LocalFileDetails* pDataDetails = NULL; + const LocalFileDetails* pRsrcDetails = NULL; + const LocalFileDetails* pDetails = pData->GetDetails(); const char* typeStr = "????"; // for debug msg only - switch (pDetails->entryKind) { - case FileDetails::kFileKindDataFork: + switch (pDetails->GetEntryKind()) { + case LocalFileDetails::kFileKindDataFork: pDataDetails = pDetails; typeStr = "data"; break; - case FileDetails::kFileKindRsrcFork: + case LocalFileDetails::kFileKindRsrcFork: pRsrcDetails = pDetails; typeStr = "rsrc"; break; - case FileDetails::kFileKindDiskImage: + case LocalFileDetails::kFileKindDiskImage: pDataDetails = pDetails; typeStr = "disk"; break; - case FileDetails::kFileKindBothForks: - case FileDetails::kFileKindDirectory: + case LocalFileDetails::kFileKindBothForks: + case LocalFileDetails::kFileKindDirectory: default: assert(false); return L"internal error"; @@ -1499,20 +1508,20 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) pDetails = pData->GetOtherFork()->GetDetails(); typeStr = "both"; - switch (pDetails->entryKind) { - case FileDetails::kFileKindDataFork: + switch (pDetails->GetEntryKind()) { + case LocalFileDetails::kFileKindDataFork: assert(pDataDetails == NULL); pDataDetails = pDetails; break; - case FileDetails::kFileKindRsrcFork: + case LocalFileDetails::kFileKindRsrcFork: assert(pRsrcDetails == NULL); pRsrcDetails = pDetails; break; - case FileDetails::kFileKindDiskImage: + case LocalFileDetails::kFileKindDiskImage: assert(false); return L"(internal) add other disk error"; - case FileDetails::kFileKindBothForks: - case FileDetails::kFileKindDirectory: + case LocalFileDetails::kFileKindBothForks: + case LocalFileDetails::kFileKindDirectory: default: assert(false); return L"internal error"; @@ -1520,7 +1529,7 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) } LOGI("Adding file '%ls' (%hs)", - (LPCWSTR) pDetails->storageName, typeStr); + (LPCWSTR) pDetails->GetStrippedLocalPathName(), typeStr); ASSERT(pDataDetails != NULL || pRsrcDetails != NULL); /* @@ -1530,14 +1539,20 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) * but it could be awkward for HFS (not to mention HFS Plus!). */ DiskFS::CreateParms parms; - ConvertFDToCP(pData->GetDetails(), &parms); + /* use the FS-normalized path here */ + /* (do we have to? do we want to?) */ + parms.pathName = pData->GetFSNormalPath(); if (pRsrcDetails != NULL) parms.storageType = kNuStorageExtended; else parms.storageType = kNuStorageSeedling; - /* use the FS-normalized path here */ - /* (do we have to? do we want to?) */ - parms.pathName = pData->GetFSNormalPath(); + /* copy the rest out of the LocalFileDetails */ + parms.fssep = pDetails->GetFssep(); + parms.fileType = pDetails->GetFileType(); + parms.auxType = pDetails->GetExtraType(); + parms.access = pDetails->GetAccess(); + parms.createWhen = NufxArchive::DateTimeToSeconds(&pDetails->GetCreateWhen()); + parms.modWhen = NufxArchive::DateTimeToSeconds(&pDetails->GetModWhen()); dataLen = rsrcLen = -1; if (pDataDetails != NULL) { @@ -1545,8 +1560,8 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) /* (HA conversion only happens if text conversion happens) */ GenericEntry::ConvertHighASCII convHA; if (addOptsConvEOL == AddFilesDialog::kConvEOLType) { - if (pDataDetails->fileType == kFileTypeTXT || - pDataDetails->fileType == kFileTypeSRC) + if (pDataDetails->GetFileType() == kFileTypeTXT || + pDataDetails->GetFileType() == kFileTypeSRC) { LOGI("Enabling text conversion by type"); convEOL = GenericEntry::kConvertEOLOn; @@ -1559,14 +1574,14 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) else convHA = GenericEntry::kConvertHAOff; - errMsg = LoadFile(pDataDetails->origName, &dataBuf, &dataLen, + errMsg = LoadFile(pDataDetails->GetLocalPathName(), &dataBuf, &dataLen, convEOL, convHA); if (!errMsg.IsEmpty()) goto bail; } if (pRsrcDetails != NULL) { /* no text conversion on resource forks */ - errMsg = LoadFile(pRsrcDetails->origName, &rsrcBuf, &rsrcLen, + errMsg = LoadFile(pRsrcDetails->GetLocalPathName(), &rsrcBuf, &rsrcLen, GenericEntry::kConvertEOLOff, GenericEntry::kConvertHAOff); if (!errMsg.IsEmpty()) goto bail; @@ -1575,7 +1590,7 @@ CString DiskArchive::ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL) /* really ought to do this separately for each thread */ SET_PROGRESS_BEGIN(); CString pathNameW(parms.pathName); - SET_PROGRESS_UPDATE2(0, pDetails->origName, pathNameW); + SET_PROGRESS_UPDATE2(0, pDetails->GetLocalPathName(), pathNameW); DIError dierr; dierr = AddForksToDisk(pDiskFS, &parms, dataBuf, dataLen, @@ -1954,25 +1969,6 @@ bail: return dierr; } -// TODO: make this a member of FileDetails, and return a struct owned by -// FileDetails. This is necessary because we put const strings into -// pCreateParms that are owned by FileDetails, and need to coordinate the -// object lifetime. -void DiskArchive::ConvertFDToCP(const FileDetails* pDetails, - DiskFS::CreateParms* pCreateParms) -{ - // ugly hack to get storage for narrow string - pDetails->fStorageNameA = pDetails->storageName; - pCreateParms->pathName = pDetails->fStorageNameA; - pCreateParms->fssep = (char) pDetails->fileSysInfo; - pCreateParms->storageType = pDetails->storageType; - pCreateParms->fileType = pDetails->fileType; - pCreateParms->auxType = pDetails->extraType; - pCreateParms->access = pDetails->access; - pCreateParms->createWhen = NufxArchive::DateTimeToSeconds(&pDetails->createWhen); - pCreateParms->modWhen = NufxArchive::DateTimeToSeconds(&pDetails->modWhen); -} - void DiskArchive::AddToAddDataList(FileAddData* pData) { ASSERT(pData != NULL); @@ -1983,31 +1979,31 @@ void DiskArchive::AddToAddDataList(FileAddData* pData) * O(n^2) behavior, but I'm expecting N to be relatively small (under * 1000 in almost all cases). */ - //if (strcasecmp(pData->GetDetails()->storageName, "system\\finder") == 0) - // LOGI("whee"); FileAddData* pSearch = fpAddDataHead; - FileDetails::FileKind dataKind, listKind; + LocalFileDetails::FileKind dataKind, listKind; - dataKind = pData->GetDetails()->entryKind; + dataKind = pData->GetDetails()->GetEntryKind(); while (pSearch != NULL) { if (pSearch->GetOtherFork() == NULL && - wcscmp(pSearch->GetDetails()->storageName, - pData->GetDetails()->storageName) == 0) + wcscmp(pSearch->GetDetails()->GetStrippedLocalPathName(), + pData->GetDetails()->GetStrippedLocalPathName()) == 0) { //NuThreadID dataID = pData->GetDetails()->threadID; //NuThreadID listID = pSearch->GetDetails()->threadID; - listKind = pSearch->GetDetails()->entryKind; + listKind = pSearch->GetDetails()->GetEntryKind(); /* got a name match */ if (dataKind != listKind && - (dataKind == FileDetails::kFileKindDataFork || dataKind == FileDetails::kFileKindRsrcFork) && - (listKind == FileDetails::kFileKindDataFork || listKind == FileDetails::kFileKindRsrcFork)) + (dataKind == LocalFileDetails::kFileKindDataFork || + dataKind == LocalFileDetails::kFileKindRsrcFork) && + (listKind == LocalFileDetails::kFileKindDataFork || + listKind == LocalFileDetails::kFileKindRsrcFork)) { /* looks good, hook it in here instead of the list */ LOGD("--- connecting forks of '%ls' and '%ls'", - (LPCWSTR) pData->GetDetails()->origName, - (LPCWSTR) pSearch->GetDetails()->origName); + (LPCWSTR) pData->GetDetails()->GetLocalPathName(), + (LPCWSTR) pSearch->GetDetails()->GetLocalPathName()); pSearch->SetOtherFork(pData); return; } @@ -2299,7 +2295,7 @@ bool DiskArchive::RenameSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED); goto bail; } - LOGI("Rename of '%ls' to '%ls' succeeded", + LOGD("Rename of '%ls' to '%ls' succeeded", pEntry->GetDisplayName(), (LPCWSTR) renameDlg.fNewName); } else if (result == IDCANCEL) { LOGI("Canceling out of remaining renames"); @@ -2528,7 +2524,7 @@ bool DiskArchive::SetProps(CWnd* pMsgWnd, GenericEntry* pGenericEntry, DiskImg::FSFormat fsFormat; fsFormat = pFile->GetDiskFS()->GetDiskImg()->GetFSFormat(); if (fsFormat == DiskImg::kFormatDOS32 || fsFormat == DiskImg::kFormatDOS33) { - LOGI(" (reloading additional fields after DOS SFI)"); + LOGD(" (reloading additional fields after DOS SFI)"); pEntry->SetDataForkLen(pFile->GetDataLength()); pEntry->SetCompressedLen(pFile->GetDataSparseLength()); pEntry->SetSuspicious(pFile->GetQuality() == A2File::kQualitySuspicious); @@ -2559,9 +2555,9 @@ GenericArchive::XferStatus DiskArchive::XferSelection(CWnd* pMsgWnd, * forked or not. */ LOGI("DiskArchive XferSelection!"); - unsigned char* dataBuf = NULL; - unsigned char* rsrcBuf = NULL; - FileDetails fileDetails; + uint8_t* dataBuf = NULL; + uint8_t* rsrcBuf = NULL; + LocalFileDetails fileDetails; CString errMsg, extractErrMsg, cmpStr; CString fixedPathName; XferStatus retval = kXferFailed; @@ -2590,7 +2586,7 @@ GenericArchive::XferStatus DiskArchive::XferSelection(CWnd* pMsgWnd, */ fixedPathName = pEntry->GetPathName(); if (fixedPathName.IsEmpty()) - fixedPathName = _T("(no filename)"); + fixedPathName = L"(no filename)"; if (pEntry->GetFSFormat() != DiskImg::kFormatProDOS) fixedPathName.Replace(PathProposal::kDefaultStoredFssep, '.'); if (pEntry->GetSubVolName() != NULL) { @@ -2603,7 +2599,7 @@ GenericArchive::XferStatus DiskArchive::XferSelection(CWnd* pMsgWnd, if (pEntry->GetRecordKind() == GenericEntry::kRecordKindVolumeDir) { /* this is the volume dir */ - LOGI(" XFER not transferring volume dir '%ls'", + LOGD(" XFER not transferring volume dir '%ls'", (LPCWSTR) fixedPathName); continue; } else if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory) { @@ -2613,22 +2609,22 @@ GenericArchive::XferStatus DiskArchive::XferSelection(CWnd* pMsgWnd, cmpStr += (char)PathProposal::kDefaultStoredFssep; if (pSelSet->CountMatchingPrefix(cmpStr) == 0) { - LOGI("FOUND empty dir '%ls'", (LPCWSTR) fixedPathName); + LOGD("FOUND empty dir '%ls'", (LPCWSTR) fixedPathName); cmpStr += kEmptyFolderMarker; dataBuf = new unsigned char[1]; dataLen = 0; - fileDetails.entryKind = FileDetails::kFileKindDataFork; - fileDetails.storageName = cmpStr; - fileDetails.fileType = 0; // NON - fileDetails.access = - pEntry->GetAccess() | GenericEntry::kAccessInvisible; + fileDetails.SetEntryKind(LocalFileDetails::kFileKindDataFork); + fileDetails.SetStrippedLocalPathName(cmpStr); + fileDetails.SetFileType(0); // NON + fileDetails.SetAccess( + pEntry->GetAccess() | GenericEntry::kAccessInvisible); goto have_stuff2; } else { - LOGI("NOT empty dir '%ls'", (LPCWSTR) fixedPathName); + LOGD("NOT empty dir '%ls'", (LPCWSTR) fixedPathName); } } - LOGI(" XFER not transferring directory '%ls'", + LOGD(" XFER not transferring directory '%ls'", (LPCWSTR) fixedPathName); continue; } @@ -2695,38 +2691,42 @@ GenericArchive::XferStatus DiskArchive::XferSelection(CWnd* pMsgWnd, ASSERT(rsrcBuf == NULL); } - if (pEntry->GetHasDataFork() && pEntry->GetHasRsrcFork()) - fileDetails.entryKind = FileDetails::kFileKindBothForks; - else if (pEntry->GetHasDataFork()) - fileDetails.entryKind = FileDetails::kFileKindDataFork; - else if (pEntry->GetHasRsrcFork()) - fileDetails.entryKind = FileDetails::kFileKindRsrcFork; - else { + if (pEntry->GetHasDataFork() && pEntry->GetHasRsrcFork()) { + fileDetails.SetEntryKind(LocalFileDetails::kFileKindBothForks); + } else if (pEntry->GetHasDataFork()) { + fileDetails.SetEntryKind(LocalFileDetails::kFileKindDataFork); + } else if (pEntry->GetHasRsrcFork()) { + fileDetails.SetEntryKind(LocalFileDetails::kFileKindRsrcFork); + } else { ASSERT(false); - fileDetails.entryKind = FileDetails::kFileKindUnknown; + fileDetails.SetEntryKind(LocalFileDetails::kFileKindUnknown); } /* - * Set up the FileDetails. + * Set up the rest of the LocalFileDetails fields. */ - fileDetails.storageName = fixedPathName; - fileDetails.fileType = pEntry->GetFileType(); - fileDetails.access = pEntry->GetAccess(); + fileDetails.SetStrippedLocalPathName(fixedPathName); + fileDetails.SetFileType(pEntry->GetFileType()); + fileDetails.SetAccess(pEntry->GetAccess()); have_stuff2: - fileDetails.fileSysFmt = pEntry->GetSourceFS(); - fileDetails.fileSysInfo = PathProposal::kDefaultStoredFssep; - fileDetails.extraType = pEntry->GetAuxType(); - fileDetails.storageType = kNuStorageUnknown; /* let NufxLib deal */ + fileDetails.SetFileSysFmt(pEntry->GetSourceFS()); + fileDetails.SetFssep(PathProposal::kDefaultStoredFssep); + fileDetails.SetExtraType(pEntry->GetAuxType()); + fileDetails.SetStorageType(kNuStorageUnknown); // let NufxLib deal + NuDateTime ndt; time_t when; when = time(NULL); - UNIXTimeToDateTime(&when, &fileDetails.archiveWhen); + UNIXTimeToDateTime(&when, &ndt); + fileDetails.SetArchiveWhen(ndt); when = pEntry->GetModWhen(); - UNIXTimeToDateTime(&when, &fileDetails.modWhen); + UNIXTimeToDateTime(&when, &ndt); + fileDetails.SetModWhen(ndt); when = pEntry->GetCreateWhen(); - UNIXTimeToDateTime(&when, &fileDetails.createWhen); + UNIXTimeToDateTime(&when, &ndt); + fileDetails.SetCreateWhen(ndt); - pActionProgress->SetArcName(fileDetails.storageName); + pActionProgress->SetArcName(fileDetails.GetStrippedLocalPathName()); if (pActionProgress->SetProgress(0) == IDCANCEL) { retval = kXferCancelled; goto bail; @@ -2780,23 +2780,20 @@ void DiskArchive::XferPrepare(const XferFileOptions* pXferOpts) fpXferTargetFS = pXferOpts->fpTargetFS; } -CString DiskArchive::XferFile(FileDetails* pDetails, uint8_t** pDataBuf, +CString DiskArchive::XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf, long dataLen, uint8_t** pRsrcBuf, long rsrcLen) { - //const int kFileTypeTXT = 0x04; - DiskFS::CreateParms createParms; DiskFS* pDiskFS; CString errMsg; DIError dierr = kDIErrNone; LOGI(" XFER: transfer '%ls' (dataLen=%ld rsrcLen=%ld)", - (LPCWSTR) pDetails->storageName, dataLen, rsrcLen); + (LPCWSTR) pDetails->GetStrippedLocalPathName(), dataLen, rsrcLen); ASSERT(pDataBuf != NULL); ASSERT(pRsrcBuf != NULL); - /* fill out CreateParms from FileDetails */ - ConvertFDToCP(pDetails, &createParms); + const DiskFS::CreateParms& createParms = pDetails->GetCreateParms(); if (fpXferTargetFS == NULL) pDiskFS = fpPrimaryDiskFS; @@ -2813,22 +2810,24 @@ CString DiskArchive::XferFile(FileDetails* pDetails, uint8_t** pDataBuf, * not worth adding a new interface just for that. */ bool srcIsDOS, dstIsDOS; - srcIsDOS = DiskImg::UsesDOSFileStructure(pDetails->fileSysFmt); + srcIsDOS = DiskImg::UsesDOSFileStructure(pDetails->GetFileSysFmt()); dstIsDOS = DiskImg::UsesDOSFileStructure(pDiskFS->GetDiskImg()->GetFSFormat()); if (dataLen > 0 && - (pDetails->fileType == kFileTypeTXT || pDetails->fileType == kFileTypeSRC)) + (pDetails->GetFileType() == kFileTypeTXT || + pDetails->GetFileType() == kFileTypeSRC)) { unsigned char* ucp = *pDataBuf; long len = dataLen; if (srcIsDOS && !dstIsDOS) { LOGD(" Stripping high ASCII from '%ls'", - (LPCWSTR) pDetails->storageName); + (LPCWSTR) pDetails->GetStrippedLocalPathName()); while (len--) *ucp++ &= 0x7f; } else if (!srcIsDOS && dstIsDOS) { - LOGD(" Adding high ASCII to '%ls'", (LPCWSTR) pDetails->storageName); + LOGD(" Adding high ASCII to '%ls'", + (LPCWSTR) pDetails->GetStrippedLocalPathName()); while (len--) { if (*ucp != '\0') @@ -2837,9 +2836,10 @@ CString DiskArchive::XferFile(FileDetails* pDetails, uint8_t** pDataBuf, } } else if (srcIsDOS && dstIsDOS) { LOGD(" --- not altering DOS-to-DOS text '%ls'", - (LPCWSTR) pDetails->storageName); + (LPCWSTR) pDetails->GetStrippedLocalPathName()); } else { - LOGD(" --- non-DOS transfer '%ls'", (LPCWSTR) pDetails->storageName); + LOGD(" --- non-DOS transfer '%ls'", + (LPCWSTR) pDetails->GetStrippedLocalPathName()); } } diff --git a/app/DiskArchive.h b/app/DiskArchive.h index 078fcdc..e91dbcc 100644 --- a/app/DiskArchive.h +++ b/app/DiskArchive.h @@ -231,7 +231,7 @@ private: virtual CString Close(void); virtual void XferPrepare(const XferFileOptions* pXferOpts) override; - virtual CString XferFile(FileDetails* pDetails, uint8_t** pDataBuf, + virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf, long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override; virtual void XferAbort(CWnd* pMsgWnd) override; virtual void XferFinish(CWnd* pMsgWnd) override; @@ -253,10 +253,10 @@ private: */ class FileAddData { public: - FileAddData(const FileDetails* pDetails, char* fsNormalPath) { + FileAddData(const LocalFileDetails* pDetails, char* fsNormalPathMOR) { fDetails = *pDetails; - fFSNormalPath = fsNormalPath; + fFSNormalPathMOR = fsNormalPathMOR; fpOtherFork = NULL; fpNext = NULL; } @@ -267,25 +267,21 @@ private: FileAddData* GetOtherFork(void) const { return fpOtherFork; } void SetOtherFork(FileAddData* pData) { fpOtherFork = pData; } - const FileDetails* GetDetails(void) const { return &fDetails; } + const LocalFileDetails* GetDetails(void) const { return &fDetails; } /* * Get the "FS-normal" path, i.e. exactly what we want to appear * on the disk image. This has the result of any conversions, so - * we need to store it as a narrow string. + * we need to store it as a narrow Mac OS Roman string. */ - const char* GetFSNormalPath(void) const { return fFSNormalPath; } + const char* GetFSNormalPath(void) const { return fFSNormalPathMOR; } private: - // Three filenames stored inside FileDetails: - // fDetails.origName -- the name of the Windows file - // fDetails.storageName -- origName with type-preservation goodies - // stripped out - // fFSNormalPath -- the FS-normalized version of "storageName", i.e. - // the name as it will appear on the Apple II disk image + LocalFileDetails fDetails; - FileDetails fDetails; - CStringA fFSNormalPath; + // The DiskFS-normalized version of the storage name. This is the + // name as it will appear on the Apple II disk image. + CStringA fFSNormalPathMOR; FileAddData* fpOtherFork; FileAddData* fpNext; @@ -293,7 +289,7 @@ private: virtual ArchiveKind GetArchiveKind(void) override { return kArchiveDiskImage; } virtual NuError DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) override; + LocalFileDetails* pDetails) override; /* * Reload the contents of the archive, showing an error message if the @@ -344,7 +340,7 @@ private: * Replaces pDetails->storageName if the user elects to rename */ NuResult HandleReplaceExisting(const A2File* pExisting, - FileDetails* pDetails); + LocalFileDetails* pDetails); /* * Process the list of pending file adds. @@ -394,15 +390,6 @@ private: */ void FreeAddDataList(void); - /* - * Fill out a CreateParms structure from a FileDetails structure. - * - * The NuStorageType values correspond exactly to ProDOS storage types, so - * there's no need to convert them. - */ - void ConvertFDToCP(const FileDetails* pDetails, - DiskFS::CreateParms* pCreateParms); - /* * Set up a RenameEntryDialog for the entry in "*pEntry". * diff --git a/app/FileNameConv.cpp b/app/FileNameConv.cpp index 6d48e80..427bf50 100644 --- a/app/FileNameConv.cpp +++ b/app/FileNameConv.cpp @@ -1287,9 +1287,11 @@ void PathProposal::DenormalizePath(WCHAR* pathBuf) ch = HexDigit(*srcp) << 4; srcp++; if (isxdigit((int)*srcp)) { - /* valid, output char */ + /* valid, output char (unless it's a %00 place-holder) */ ch += HexDigit(*srcp); - *dstp++ = ch; + if (ch != '\0') { + *dstp++ = ch; + } srcp++; } else { /* bogus '%' with trailing hex digit found! */ diff --git a/app/GenericArchive.cpp b/app/GenericArchive.cpp index ab64716..a834d80 100644 --- a/app/GenericArchive.cpp +++ b/app/GenericArchive.cpp @@ -10,6 +10,7 @@ */ #include "stdafx.h" #include "GenericArchive.h" +#include "NufxArchive.h" #include "FileNameConv.h" #include "ContentList.h" #include "Main.h" @@ -46,36 +47,34 @@ */ GenericEntry::GenericEntry(void) + : fPathName(NULL), + fFileName(NULL), + fFileNameExtension(NULL), + fFssep('\0'), + fSubVolName(NULL), + fDisplayName(NULL), + fFileType(0), + fAuxType(0), + fAccess(0), + fCreateWhen(kDateNone), + fModWhen(kDateNone), + fRecordKind(kRecordKindUnknown), + fFormatStr(L"Unknown"), + fDataForkLen(0), + fRsrcForkLen(0), + fCompressedLen(0), + fSourceFS(DiskImg::kFormatUnknown), + fHasDataFork(false), + fHasRsrcFork(false), + fHasDiskImage(false), + fHasComment(false), + fHasNonEmptyComment(false), + fDamaged(false), + fSuspicious(false), + fIndex(-1), + fpPrev(NULL), + fpNext(NULL) { - fPathName = NULL; - fFileName = NULL; - fFssep = '\0'; - fSubVolName = NULL; - fDisplayName = NULL; - fFileType = 0; - fAuxType = 0; - fAccess = 0; - fModWhen = kDateNone; - fCreateWhen = kDateNone; - fRecordKind = kRecordKindUnknown; - fFormatStr = L"Unknown"; - fCompressedLen = 0; - //fUncompressedLen = 0; - fDataForkLen = fRsrcForkLen = 0; - - fSourceFS = DiskImg::kFormatUnknown; - - fHasDataFork = false; - fHasRsrcFork = false; - fHasDiskImage = false; - fHasComment = false; - fHasNonEmptyComment = false; - - fIndex = -1; - fpPrev = NULL; - fpNext = NULL; - - fDamaged = fSuspicious = false; } GenericEntry::~GenericEntry(void) @@ -177,7 +176,7 @@ const WCHAR* GenericEntry::GetFileTypeString(void) const } /*static*/ bool GenericEntry::CheckHighASCII(const uint8_t* buffer, - unsigned long count) + size_t count) { /* * (Pulled from NufxLib Funnel.c.) @@ -254,7 +253,7 @@ static const char kCharLF = '\n'; static const char kCharCR = '\r'; /*static*/ GenericEntry::ConvertEOL GenericEntry::DetermineConversion( - const uint8_t* buffer, long count, + const uint8_t* buffer, size_t count, EOLType* pSourceType, ConvertHighASCII* pConvHA) { /* @@ -277,7 +276,7 @@ static const char kCharCR = '\r'; * it will be stripped *before* the EOL determination is made. */ ConvertHighASCII wantConvHA = *pConvHA; - long bufCount, numBinary, numLF, numCR; + size_t bufCount, numBinary, numLF, numCR; bool isHighASCII; uint8_t val; @@ -367,7 +366,7 @@ static inline void PutEOL(FILE* fp) { int err = 0; - LOGI("+++ WriteConvert conv=%d convHA=%d", *pConv, *pConvHA); + LOGD("+++ WriteConvert conv=%d convHA=%d", *pConv, *pConvHA); if (len == 0) { LOGI("WriteConvert asked to write 0 bytes; returning"); @@ -396,7 +395,7 @@ static inline void PutEOL(FILE* fp) *pConvHA = kConvertHAOff; } } - LOGI("+++ After auto, conv=%d convHA=%d", *pConv, *pConvHA); + LOGD("+++ After auto, conv=%d convHA=%d", *pConv, *pConvHA); ASSERT(*pConv == kConvertEOLOn || *pConv == kConvertEOLOff); ASSERT(*pConvHA == kConvertHAOn || *pConvHA == kConvertHAOff); @@ -404,7 +403,7 @@ static inline void PutEOL(FILE* fp) if (*pConv == kConvertEOLOff) { if (fwrite(buf, len, 1, fp) != 1) { err = errno; - LOGI("WriteConvert failed, err=%d", errno); + LOGE("WriteConvert failed, err=%d", errno); } } else { ASSERT(*pConv == kConvertEOLOn); @@ -546,7 +545,7 @@ GenericArchive::CreateIndex(void) mangle.Delete(idx+1, len-(idx+1)); /* delete out to the end */ mangle += kTmpTemplate; } - LOGI("GenDerived: passed '%ls' returned '%ls'", filename, (LPCWSTR) mangle); + LOGD("GenDerived: passed '%ls' returned '%ls'", filename, (LPCWSTR) mangle); return mangle; } @@ -594,13 +593,9 @@ GenericArchive::CreateIndex(void) */ /* - * This comes straight out of NuLib2, and uses NufxLib data structures. While - * it may seem strange to use these structures for non-NuFX archives, they are - * convenient and hold at least as much information as any other format needs. + * Much of this was adapted from NuLib2. */ -typedef bool Boolean; - /*static*/ void GenericArchive::UNIXTimeToDateTime(const time_t* pWhen, NuDateTime* pDateTime) { @@ -625,99 +620,6 @@ typedef bool Boolean; pDateTime->weekDay = ptm->tm_wday +1; } -NuError GenericArchive::GetFileDetails(const AddFilesDialog* pAddOpts, - const WCHAR* pathname, struct _stat* psb, FileDetails* pDetails) -{ - //char* livePathStr; - time_t now; - - ASSERT(pAddOpts != NULL); - ASSERT(pathname != NULL); - ASSERT(pDetails != NULL); - - /* init to defaults */ - //pDetails->threadID = kNuThreadIDDataFork; - pDetails->entryKind = FileDetails::kFileKindDataFork; - //pDetails->fileSysID = kNuFileSysUnknown; - pDetails->fileSysFmt = DiskImg::kFormatUnknown; - pDetails->fileSysInfo = PathProposal::kDefaultStoredFssep; - pDetails->fileType = 0; - pDetails->extraType = 0; - pDetails->storageType = kNuStorageUnknown; /* let NufxLib worry about it */ - if (psb->st_mode & S_IWUSR) - pDetails->access = kNuAccessUnlocked; - else - pDetails->access = kNuAccessLocked; - -#if 0 - /* if this is a disk image, fill in disk-specific fields */ - if (NState_GetModAddAsDisk(pState)) { - if ((psb->st_size & 0x1ff) != 0) { - /* reject anything whose size isn't a multiple of 512 bytes */ - printf("NOT storing odd-sized (%ld) file as disk image: %ls\n", - (long)psb->st_size, livePathStr); - } else { - /* set fields; note the "preserve" stuff can override this */ - pDetails->threadID = kNuThreadIDDiskImage; - pDetails->storageType = 512; - pDetails->extraType = psb->st_size / 512; - } - } -#endif - - now = time(NULL); - UNIXTimeToDateTime(&now, &pDetails->archiveWhen); - UNIXTimeToDateTime(&psb->st_mtime, &pDetails->modWhen); - UNIXTimeToDateTime(&psb->st_ctime, &pDetails->createWhen); - - /* get adjusted filename, along with any preserved type info */ - PathProposal pathProp; - pathProp.Init(pathname); - pathProp.LocalToArchive(pAddOpts); - - /* set up the local and archived pathnames */ - pDetails->storageName = ""; - if (!pAddOpts->fStoragePrefix.IsEmpty()) { - pDetails->storageName += pAddOpts->fStoragePrefix; - pDetails->storageName += pathProp.fStoredFssep; - } - pDetails->storageName += pathProp.fStoredPathName; - - /* - * Fill in the NuFileDetails struct. - * - * We use GetBuffer to get the string to ensure that the CString object - * doesn't do anything while we're not looking. The string won't be - * modified, and it won't be around for very long, so it's not strictly - * necessary to do this. It is, however, the correct approach. - */ - pDetails->origName = pathname; - pDetails->fileSysInfo = pathProp.fStoredFssep; - pDetails->fileType = pathProp.fFileType; - pDetails->extraType = pathProp.fAuxType; - switch (pathProp.fThreadKind) { - case GenericEntry::kDataThread: - //pDetails->threadID = kNuThreadIDDataFork; - pDetails->entryKind = FileDetails::kFileKindDataFork; - break; - case GenericEntry::kRsrcThread: - //pDetails->threadID = kNuThreadIDRsrcFork; - pDetails->entryKind = FileDetails::kFileKindRsrcFork; - break; - case GenericEntry::kDiskImageThread: - //pDetails->threadID = kNuThreadIDDiskImage; - pDetails->entryKind = FileDetails::kFileKindDiskImage; - break; - default: - ASSERT(false); - // was initialized to default earlier - break; - } - -/*bail:*/ - return kNuErrNone; -} - /* * Directory structure and functions, based on zDIR in Info-Zip sources. */ @@ -864,8 +766,8 @@ NuError GenericArchive::Win32AddFile(const AddFilesDialog* pAddOpts, const WCHAR* pathname, CString* pErrMsg) { NuError err = kNuErrNone; - Boolean exists, isDir, isReadable; - FileDetails details; + bool exists, isDir, isReadable; + LocalFileDetails details; struct _stat sb; ASSERT(pAddOpts != NULL); @@ -901,18 +803,16 @@ NuError GenericArchive::Win32AddFile(const AddFilesDialog* pAddOpts, * filetype and auxtype it has, and whether or not it's actually the * resource fork of another file. */ - LOGI("+++ ADD '%ls'", pathname); + LOGD("+++ ADD '%ls'", pathname); /* - * Fill out the "details" structure. The class has an automatic - * conversion to NuFileDetails, but it relies on the CString storage - * in the FileDetails, so be careful how you use it. + * Fill out the "details" structure. */ - err = GetFileDetails(pAddOpts, pathname, &sb, &details); + err = details.SetFields(pAddOpts, pathname, &sb); if (err != kNuErrNone) goto bail; - assert(wcscmp(pathname, details.origName) == 0); + assert(wcscmp(pathname, details.GetLocalPathName()) == 0); err = DoAddFile(pAddOpts, &details); if (err == kNuErrSkipped) // ignore "skipped" result err = kNuErrNone; @@ -934,80 +834,144 @@ NuError GenericArchive::AddFile(const AddFilesDialog* pAddOpts, return Win32AddFile(pAddOpts, pathname, pErrMsg); } + /* * =========================================================================== * GenericArchive::FileDetails * =========================================================================== */ -GenericArchive::FileDetails::FileDetails(void) +GenericArchive::LocalFileDetails::LocalFileDetails(void) + : fEntryKind(kFileKindUnknown), + fFileSysFmt(DiskImg::kFormatUnknown), + fFssep('\0'), + fFileType(0), + fExtraType(0), + fAccess(0), + fStorageType(0) { - //threadID = 0; - entryKind = kFileKindUnknown; - fileSysFmt = DiskImg::kFormatUnknown; - fileSysInfo = storageType = 0; - access = fileType = extraType = 0; - memset(&createWhen, 0, sizeof(createWhen)); - memset(&modWhen, 0, sizeof(modWhen)); - memset(&archiveWhen, 0, sizeof(archiveWhen)); + memset(&fCreateWhen, 0, sizeof(fCreateWhen)); + memset(&fModWhen, 0, sizeof(fModWhen)); + memset(&fArchiveWhen, 0, sizeof(fArchiveWhen)); + + // set these for debugging + memset(&fNuFileDetails, 0xcc, sizeof(fNuFileDetails)); + memset(&fCreateParms, 0xcc, sizeof(&fCreateParms)); + fStoragePathNameMOR = "!INIT!"; } -GenericArchive::FileDetails::operator const NuFileDetails() const +NuError GenericArchive::LocalFileDetails::SetFields(const AddFilesDialog* pAddOpts, + const WCHAR* pathname, struct _stat* psb) { - NuFileDetails details; + time_t now; - //details.threadID = threadID; - switch (entryKind) { - case kFileKindDataFork: - details.threadID = kNuThreadIDDataFork; + ASSERT(pAddOpts != NULL); + ASSERT(pathname != NULL); + + /* get adjusted filename, along with any preserved type info */ + PathProposal pathProp; + pathProp.Init(pathname); + pathProp.LocalToArchive(pAddOpts); + + /* set up the local and archived pathnames */ + fLocalPathName = pathname; + fStrippedLocalPathName = L""; + if (!pAddOpts->fStoragePrefix.IsEmpty()) { + fStrippedLocalPathName += pAddOpts->fStoragePrefix; + fStrippedLocalPathName += pathProp.fStoredFssep; + } + fStrippedLocalPathName += pathProp.fStoredPathName; + GenerateStoragePathName(); + + fFileSysFmt = DiskImg::kFormatUnknown; + fStorageType = kNuStorageUnknown; /* let NufxLib et.al. worry about it */ + if (psb->st_mode & S_IWUSR) + fAccess = kNuAccessUnlocked; + else + fAccess = kNuAccessLocked; + fEntryKind = LocalFileDetails::kFileKindDataFork; + fFssep = pathProp.fStoredFssep; + fFileType = pathProp.fFileType; + fExtraType = pathProp.fAuxType; + +#if 0 + /* if this is a disk image, fill in disk-specific fields */ + if (NState_GetModAddAsDisk(pState)) { + if ((psb->st_size & 0x1ff) != 0) { + /* reject anything whose size isn't a multiple of 512 bytes */ + printf("NOT storing odd-sized (%ld) file as disk image: %ls\n", + (long)psb->st_size, livePathStr); + } else { + /* set fields; note the "preserve" stuff can override this */ + pDetails->threadID = kNuThreadIDDiskImage; + pDetails->storageType = 512; + pDetails->extraType = psb->st_size / 512; + } + } +#endif + + now = time(NULL); + UNIXTimeToDateTime(&now, &fArchiveWhen); + UNIXTimeToDateTime(&psb->st_mtime, &fModWhen); + UNIXTimeToDateTime(&psb->st_ctime, &fCreateWhen); + + switch (pathProp.fThreadKind) { + case GenericEntry::kDataThread: + //pDetails->threadID = kNuThreadIDDataFork; + fEntryKind = LocalFileDetails::kFileKindDataFork; break; - case kFileKindBothForks: // not exactly supported, doesn't really matter - case kFileKindRsrcFork: - details.threadID = kNuThreadIDRsrcFork; + case GenericEntry::kRsrcThread: + //pDetails->threadID = kNuThreadIDRsrcFork; + fEntryKind = LocalFileDetails::kFileKindRsrcFork; break; - case kFileKindDiskImage: - details.threadID = kNuThreadIDDiskImage; + case GenericEntry::kDiskImageThread: + //pDetails->threadID = kNuThreadIDDiskImage; + fEntryKind = LocalFileDetails::kFileKindDiskImage; break; - case kFileKindDirectory: default: - LOGW("Invalid entryKind (%d) for NuFileDetails conversion", - entryKind); ASSERT(false); - details.threadID = 0; // that makes it an old-style comment?! + // was initialized to default earlier break; } - // The NuFileDetails origName field was added to NufxLib so that CiderPress - // could receive the original Windows pathname in the NuErrorStatus - // callback. This is a fairly compelling argument for making origName - // an opaque field. - // - // Whatever the case, we need to pass narrow-string versions of the - // names into NufxLib via the NuFileDetails struct. Since we're returning - // the NuFileDetails struct, the strings will out-live the scope of this - // function, so we have to store them somewhere else. There's nowhere - // in NuFileDetails, so we have to keep them in FileDetails, which currently - // exposes all of its fields publicly. I'm going to kluge it for now and - // just "snapshot" the wide strings into narrow-string fields. This is - // all pretty rickety and needs to be redone. +/*bail:*/ + return kNuErrNone; +} - // TODO: make origName an opaque (void*) field in NufxLib and pass the - // wide char representation through - fOrigNameA = origName; - details.origName = fOrigNameA; - fStorageNameA = storageName; - details.storageNameMOR = fStorageNameA; - //details.fileSysID = fileSysID; - details.fileSysInfo = fileSysInfo; - details.access = access; - details.fileType = fileType; - details.extraType = extraType; - details.storageType = storageType; - details.createWhen = createWhen; - details.modWhen = modWhen; - details.archiveWhen = archiveWhen; +const NuFileDetails& GenericArchive::LocalFileDetails::GetNuFileDetails() +{ + //details.threadID = threadID; + switch (fEntryKind) { + case kFileKindDataFork: + fNuFileDetails.threadID = kNuThreadIDDataFork; + break; + case kFileKindBothForks: // not exactly supported, doesn't really matter + case kFileKindRsrcFork: + fNuFileDetails.threadID = kNuThreadIDRsrcFork; + break; + case kFileKindDiskImage: + fNuFileDetails.threadID = kNuThreadIDDiskImage; + break; + case kFileKindDirectory: + default: + LOGW("Invalid entryKind (%d) for NuFileDetails conversion", fEntryKind); + ASSERT(false); + fNuFileDetails.threadID = 0; // that makes it an old-style comment?! + break; + } - switch (fileSysFmt) { + fNuFileDetails.origName = (LPCWSTR) fLocalPathName; + fNuFileDetails.storageNameMOR = (LPCSTR) fStoragePathNameMOR; + fNuFileDetails.fileSysInfo = fFssep; + fNuFileDetails.access = fAccess; + fNuFileDetails.fileType = fFileType; + fNuFileDetails.extraType = fExtraType; + fNuFileDetails.storageType = fStorageType; + fNuFileDetails.createWhen = fCreateWhen; + fNuFileDetails.modWhen = fModWhen; + fNuFileDetails.archiveWhen = fArchiveWhen; + + switch (fFileSysFmt) { case DiskImg::kFormatProDOS: case DiskImg::kFormatDOS33: case DiskImg::kFormatDOS32: @@ -1021,42 +985,65 @@ GenericArchive::FileDetails::operator const NuFileDetails() const //kFormatHighSierra case DiskImg::kFormatISO9660: /* these map directly */ - details.fileSysID = (enum NuFileSysID) fileSysFmt; + fNuFileDetails.fileSysID = (enum NuFileSysID) fFileSysFmt; break; case DiskImg::kFormatRDOS33: case DiskImg::kFormatRDOS32: case DiskImg::kFormatRDOS3: /* these look like DOS33, e.g. text is high-ASCII */ - details.fileSysID = kNuFileSysDOS33; + fNuFileDetails.fileSysID = kNuFileSysDOS33; break; default: - details.fileSysID = kNuFileSysUnknown; + fNuFileDetails.fileSysID = kNuFileSysUnknown; break; } - // Return stack copy, which copies into compiler temporary with our - // copy constructor. - return details; + return fNuFileDetails; } -/*static*/ void GenericArchive::FileDetails::CopyFields(FileDetails* pDst, - const FileDetails* pSrc) +const DiskFS::CreateParms& GenericArchive::LocalFileDetails::GetCreateParms() { - //pDst->threadID = pSrc->threadID; - pDst->entryKind = pSrc->entryKind; - pDst->origName = pSrc->origName; - pDst->storageName = pSrc->storageName; - pDst->fileSysFmt = pSrc->fileSysFmt; - pDst->fileSysInfo = pSrc->fileSysInfo; - pDst->access = pSrc->access; - pDst->fileType = pSrc->fileType; - pDst->extraType = pSrc->extraType; - pDst->storageType = pSrc->storageType; - pDst->createWhen = pSrc->createWhen; - pDst->modWhen = pSrc->modWhen; - pDst->archiveWhen = pSrc->archiveWhen; + fCreateParms.pathName = (LPCSTR) fStoragePathNameMOR; + fCreateParms.fssep = fFssep; + fCreateParms.storageType = fStorageType; + fCreateParms.fileType = fFileType; + fCreateParms.auxType = fExtraType; + fCreateParms.access = fAccess; + fCreateParms.createWhen = NufxArchive::DateTimeToSeconds(&fCreateWhen); + fCreateParms.modWhen = NufxArchive::DateTimeToSeconds(&fModWhen); + return fCreateParms; +} + +void GenericArchive::LocalFileDetails::GenerateStoragePathName() +{ + // TODO(Unicode): generate MOR name from Unicode, instead of just + // doing a generic CP-1252 conversion. We need to do this on both + // sides though, so until we can extract MOR->Unicode we don't + // want to add Unicode->MOR. And it all depends on NufxLib and + // DiskImg being able to handle UTF-16 filenames. + fStoragePathNameMOR = fStrippedLocalPathName; +} + + +/*static*/ void GenericArchive::LocalFileDetails::CopyFields(LocalFileDetails* pDst, + const LocalFileDetails* pSrc) +{ + // don't copy fNuFileDetails, fCreateParms + pDst->fEntryKind = pSrc->fEntryKind; + pDst->fLocalPathName = pSrc->fLocalPathName; + pDst->fStrippedLocalPathName = pSrc->fStrippedLocalPathName; + pDst->fStoragePathNameMOR = pSrc->fStoragePathNameMOR; + pDst->fFileSysFmt = pSrc->fFileSysFmt; + pDst->fFssep = pSrc->fFssep; + pDst->fAccess = pSrc->fAccess; + pDst->fFileType = pSrc->fFileType; + pDst->fExtraType = pSrc->fExtraType; + pDst->fStorageType = pSrc->fStorageType; + pDst->fCreateWhen = pSrc->fCreateWhen; + pDst->fModWhen = pSrc->fModWhen; + pDst->fArchiveWhen = pSrc->fArchiveWhen; } @@ -1073,7 +1060,7 @@ void SelectionSet::CreateFromSelection(ContentList* pContentList, int threadMask * (at least under Win2K), which is a good thing. It appears that, if you * just grab indices 0..N, you will get them in order. */ - LOGI("CreateFromSelection (threadMask=0x%02x)", threadMask); + LOGD("CreateFromSelection (threadMask=0x%02x)", threadMask); POSITION posn; posn = pContentList->GetFirstSelectedItemPosition(); @@ -1090,7 +1077,7 @@ void SelectionSet::CreateFromSelection(ContentList* pContentList, int threadMask void SelectionSet::CreateFromAll(ContentList* pContentList, int threadMask) { - LOGI("CreateFromAll (threadMask=0x%02x)", threadMask); + LOGD("CreateFromAll (threadMask=0x%02x)", threadMask); int count = pContentList->GetItemCount(); for (int idx = 0; idx < count; idx++) { @@ -1104,7 +1091,7 @@ void SelectionSet::AddToSet(GenericEntry* pEntry, int threadMask) { SelectionEntry* pSelEntry; - //LOGI(" Sel '%ls'", pEntry->GetPathName()); + LOGV(" Sel '%ls'", pEntry->GetPathName()); if (!(threadMask & GenericEntry::kAllowVolumeDir) && pEntry->GetRecordKind() == GenericEntry::kRecordKindVolumeDir) @@ -1173,7 +1160,7 @@ void SelectionSet::DeleteEntries(void) SelectionEntry* pEntry; SelectionEntry* pNext; - LOGI("Deleting selection entries"); + LOGD("Deleting selection entries"); pEntry = GetEntries(); while (pEntry != NULL) { diff --git a/app/GenericArchive.h b/app/GenericArchive.h index f324865..9ba4ad3 100644 --- a/app/GenericArchive.h +++ b/app/GenericArchive.h @@ -221,7 +221,6 @@ public: LONGLONG GetUncompressedLen(void) const { return fDataForkLen + fRsrcForkLen; } - //void SetUncompressedLen(LONGLONG len) { fUncompressedLen = len; } LONGLONG GetDataForkLen(void) const { return fDataForkLen; } void SetDataForkLen(LONGLONG len) { fDataForkLen = len; } LONGLONG GetRsrcForkLen(void) const { return fRsrcForkLen; } @@ -259,8 +258,7 @@ public: /* * Check to see if this is a high-ASCII file. */ - static bool CheckHighASCII(const uint8_t* buffer, - unsigned long count); + static bool CheckHighASCII(const uint8_t* buffer, size_t count); /* * Decide, based on the contents of the buffer, whether we should do an @@ -269,7 +267,7 @@ public: * Returns kConvEOLOff or kConvEOLOn. */ static ConvertEOL DetermineConversion(const uint8_t* buffer, - long count, EOLType* pSourceType, ConvertHighASCII* pConvHA); + size_t count, EOLType* pSourceType, ConvertHighASCII* pConvHA); /* * Write data to a file, possibly converting EOL markers to Windows CRLF @@ -309,7 +307,6 @@ private: time_t fModWhen; RecordKind fRecordKind; // forked file, disk image, ?? const WCHAR* fFormatStr; // static str; compression or fs format - //LONGLONG fUncompressedLen; LONGLONG fDataForkLen; // also for disk images LONGLONG fRsrcForkLen; // set to 0 when nonexistent LONGLONG fCompressedLen; // data/disk + rsrc @@ -515,111 +512,202 @@ public: void AddEntry(GenericEntry* pEntry); /* - * This class holds details about a file that we're adding. - * - * It's based on the NuFileDetails class from NufxLib (which used to be - * used everywhere). - * - * TODO: fix this - * TODO: may want to hold a raw 8-bit pathname for copy & paste, which - * doesn't need to convert in and out of MacRoman + * This class holds details about a file that we're adding from local disk. */ - class FileDetails { + class LocalFileDetails { public: - FileDetails(void); - virtual ~FileDetails(void) {} + LocalFileDetails(void); + virtual ~LocalFileDetails(void) {} /* - * Automatic cast to NuFileDetails. The NuFileDetails structure will - * have a pointer to at least one of our strings, so structures - * filled out this way need to be short-lived. (Yes, this is - * annoying, but it's how NufxLib works.) - * - * TODO: make this a "GenNuFileDetails" method that returns a - * NuFileDetails struct owned by this object. It fills out the - * fields and references internal strings. Because we own the - * object, we can control the lifespan of the NuFileDetails, and - * ensure it doesn't live longer than our strings. + * Set the various fields, based on the pathname and characteristics + * of the file. */ - operator const NuFileDetails() const; - - /* - * Provide operator= and copy constructor. This'd be easier without - * the strings. - */ - FileDetails& operator=(const FileDetails& src) { - if (&src != this) - CopyFields(this, &src); - return *this; - } - FileDetails(const FileDetails& src) { - CopyFields(this, &src); - } + NuError SetFields(const AddFilesDialog* pAddOpts, const WCHAR* pathname, + struct _stat* psb); /* * What kind of file this is. Files being added to NuFX from Windows - * can't be "BothForks" because each the forks are stored in + * can't be "BothForks" because the forks are stored in * separate files. However, files being transferred from a NuFX * archive, a disk image, or in from the clipboard can be both. * * (NOTE: this gets embedded into clipboard data. If you change - * these values, update the version info in Clipboard.cpp.) + * these values, update the version number in Clipboard.cpp.) */ - typedef enum FileKind { + enum FileKind { kFileKindUnknown = 0, kFileKindDataFork, kFileKindRsrcFork, kFileKindDiskImage, kFileKindBothForks, kFileKindDirectory, - } FileKind; + }; /* - * Data fields. While transitioning from general use of NuFileDetails - * (v1.2.x to v2.0) I'm just going to leave these public. - * TODO: make this not public + * Provide operator= and copy constructor. */ - - //NuThreadID threadID; /* data, rsrc, disk img? */ - FileKind entryKind; + LocalFileDetails& operator=(const LocalFileDetails& src) { + if (&src != this) + CopyFields(this, &src); + return *this; + } + LocalFileDetails(const LocalFileDetails& src) { + CopyFields(this, &src); + } /* - * Original full pathname as found on Windows. - */ - CString origName; - - /* - * "Normalized" pathname. This is the full path with any of our - * added bits removed (e.g. file type & fork identifiers). It has - * not been sanitized for any specific target filesystem. + * Returns a reference to a NuFileDetails structure with the contents + * of the LocalFileDetails. * - * This is generated by PathProposal::LocalToArchive(). + * The returned structure may not be used after the LocalFileDetails + * is modified or released. */ - CString storageName; + const NuFileDetails& GetNuFileDetails(); - //NuFileSysID fileSysID; - DiskImg::FSFormat fileSysFmt; - uint16_t fileSysInfo; /* fssep lurks here */ - uint32_t access; - uint32_t fileType; - uint32_t extraType; - uint16_t storageType; /* "Unknown" or disk block size */ - NuDateTime createWhen; - NuDateTime modWhen; - NuDateTime archiveWhen; + /* + * Returns a reference to a NuFileDetails structure with the contents + * of the LocalFileDetails. + * + * The returned structure may not be used after the LocalFileDetails + * is modified or released. + */ + const DiskFS::CreateParms& GetCreateParms(); + /* + * Returns the "local" pathname, i.e. the name of a Windows file. + */ + const CString& GetLocalPathName() const { + return fLocalPathName; + } - // temporary kluge to get things working - mutable CStringA fOrigNameA; - mutable CStringA fStorageNameA; + void SetLocalPathName(const CString& newName) { + fLocalPathName = newName; + } + + /* + * This is the "local" pathname with any file type preservation + * strings removed. + */ + const CString& GetStrippedLocalPathName() const { + return fStrippedLocalPathName; + } + + /* + * Sets the "stripped" local path name, and updates the MOR version. + * Does not alter the full local path name. + */ + void SetStrippedLocalPathName(const CString& newName) { + fStrippedLocalPathName = newName; + GenerateStoragePathName(); + } + + /* + * Returns a copy of the stripped local pathname that has been + * converted to Mac OS Roman. + * + * The returned string will be invalid if SetStrippedLocalPathName + * is subsequently called. + */ + const CStringA& GetStoragePathNameMOR() const { + return fStoragePathNameMOR; + } + + FileKind GetEntryKind() const { return fEntryKind; } + void SetEntryKind(FileKind kind) { fEntryKind = kind; } + + DiskImg::FSFormat GetFileSysFmt() const { return fFileSysFmt; } + void SetFileSysFmt(DiskImg::FSFormat fmt) { fFileSysFmt = fmt; } + + char GetFssep() const { return fFssep; } + void SetFssep(char fssep) { fFssep = fssep; } + + uint32_t GetFileType() const { return fFileType; } + void SetFileType(uint32_t type) { fFileType = type; } + + uint32_t GetExtraType() const { return fExtraType; } + void SetExtraType(uint32_t type) { fExtraType = type; } + + uint32_t GetAccess() const { return fAccess; } + void SetAccess(uint32_t access) { fAccess = access; } + + uint16_t GetStorageType() const { return fStorageType; } + void SetStorageType(uint16_t type) { fStorageType = type; } + + const NuDateTime& GetArchiveWhen() const { return fArchiveWhen; } + void SetArchiveWhen(const NuDateTime& when) { fArchiveWhen = when; } + + const NuDateTime& GetModWhen() const { return fModWhen; } + void SetModWhen(const NuDateTime& when) { fModWhen = when; } + + const NuDateTime& GetCreateWhen() const { return fCreateWhen; } + void SetCreateWhen(const NuDateTime& when) { fCreateWhen = when; } private: /* - * Copy the contents of our object to a new object. + * Copy the contents of our object to a new object, field by field, + * so the CStrings copy correctly. * * Useful for operator= and copy construction. */ - static void CopyFields(FileDetails* pDst, const FileDetails* pSrc); + static void CopyFields(LocalFileDetails* pDst, + const LocalFileDetails* pSrc); + + /* + * Generates fStoragePathNameMOR from fStrippedLocalPathName. + */ + void GenerateStoragePathName(); + + /* + * These are the structs that the libraries want, so we provide calls + * that populate them and return a reference. These are "hairy" + * structures, so we have to place limitations on their lifetime. + * + * Ideally these would either be proper objects with destructors, + * so we could create them and not worry about who will free up the + * hairy bits, or we would omit the hairy bits from the structure and + * pass them separately. + */ + NuFileDetails fNuFileDetails; + DiskFS::CreateParms fCreateParms; + + /* + * What does this entry represent? + */ + FileKind fEntryKind; + + /* + * Full pathname of the Windows file. + */ + CString fLocalPathName; // was origName + + /* + * "Stripped" pathname. This is the full path with any of our + * added bits removed (e.g. file type & fork identifiers). + * + * This is generated by PathProposal::LocalToArchive(). + */ + CString fStrippedLocalPathName; // was storageName + + /* + * The storage name, generated by converting strippedLocalPathName + * from Unicode to Mac OS Roman. This is what will be passed to + * DiskImg and NufxLib as the name of the file to create in the + * archive. For DiskImg there's an additional "normalization" pass + * make the filename suitable for use on the filesystem; that name + * is stored in a FileAddData object, not here. + */ + CStringA fStoragePathNameMOR; + + DiskImg::FSFormat fFileSysFmt; // only set for xfers? + uint8_t fFssep; + uint32_t fFileType; + uint32_t fExtraType; + uint32_t fAccess; + uint16_t fStorageType; // "Unknown" or disk block size + NuDateTime fCreateWhen; + NuDateTime fModWhen; + NuDateTime fArchiveWhen; }; // Prepare for file transfers. @@ -634,7 +722,7 @@ public: // On success, *pDataBuf and *pRsrcBuf are freed and set to NULL. (It's // necessary for the interface to work this way because the NufxArchive // version just tucks the pointers into NufxLib structures.) - virtual CString XferFile(FileDetails* pDetails, uint8_t** pDataBuf, + virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf, long dataLen, uint8_t** pRsrcBuf, long rsrcLen) = 0; // Abort progress. Not all subclasses are capable of "undo". @@ -656,16 +744,6 @@ protected: void ReplaceFssep(WCHAR* str, char oldc, char newc, char newSubst); - /* - * Set the contents of a NuFileDetails structure, based on the pathname - * and characteristics of the file. - * - * For efficiency and simplicity, the pathname fields are set to CStrings in - * the GenericArchive object instead of newly-allocated storage. - */ - NuError GetFileDetails(const AddFilesDialog* pAddOpts, const WCHAR* pathname, - struct _stat* psb, FileDetails* pDetails); - /* * Prepare a directory for reading. * @@ -725,7 +803,7 @@ protected: * information in "*pDetails" may be modified. */ virtual NuError DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) = 0; + LocalFileDetails* pDetails) = 0; void SetPathName(const WCHAR* pathName) { free(fPathName); diff --git a/app/Main.cpp b/app/Main.cpp index 0360222..7886cad 100644 --- a/app/Main.cpp +++ b/app/Main.cpp @@ -1844,7 +1844,7 @@ int MainWindow::LoadArchive(const WCHAR* fileName, const WCHAR* extension, GenericArchive::OpenResult openResult; int result = -1; GenericArchive* pOpenArchive = NULL; - int origFilterIndex = filterIndex; + const int origFilterIndex = filterIndex; CString errStr, appName; CheckedLoadString(&appName, IDS_MB_APP_NAME); @@ -1883,7 +1883,7 @@ try_again: if (filterIndex == kFilterIndexBinaryII) { /* try Binary II and nothing else */ ASSERT(!createFile); - LOGI(" Trying Binary II"); + LOGD(" Trying Binary II"); pOpenArchive = new BnyArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); if (openResult != GenericArchive::kResultSuccess) { @@ -1896,7 +1896,7 @@ try_again: if (filterIndex == kFilterIndexACU) { /* try ACU and nothing else */ ASSERT(!createFile); - LOGI(" Trying ACU"); + LOGD(" Trying ACU"); pOpenArchive = new AcuArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); if (openResult != GenericArchive::kResultSuccess) { @@ -1909,7 +1909,7 @@ try_again: if (filterIndex == kFilterIndexDiskImage) { /* try various disk image formats */ ASSERT(!createFile); - LOGI(" Trying disk images"); + LOGD(" Trying disk images"); pOpenArchive = new DiskArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); @@ -1933,13 +1933,13 @@ try_again: } } else if (openResult != GenericArchive::kResultSuccess) { - if (filterIndex != origFilterIndex) { - /* - * Kluge: assume we guessed disk image and were wrong. - */ - errStr = L"File doesn't appear to be a valid archive" - L" or disk image."; - } + //if (filterIndex != origFilterIndex) { + // /* + // * Kluge: assume we guessed disk image and were wrong. + // */ + // errStr = L"File doesn't appear to be a valid archive" + // L" or disk image."; + //} if (!errStr.IsEmpty()) ShowFailureMsg(this, errStr, IDS_FAILED); result = -1; @@ -1948,7 +1948,7 @@ try_again: } else if (filterIndex == kFilterIndexNuFX) { /* try NuFX (including its embedded-in-BNY form) */ - LOGI(" Trying NuFX"); + LOGD(" Trying NuFX"); pOpenArchive = new NufxArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); diff --git a/app/Main.h b/app/Main.h index b8613df..70556a5 100644 --- a/app/Main.h +++ b/app/Main.h @@ -239,10 +239,10 @@ public: * * On failure, returns with an error message in errMsg. */ - static bool SaveToArchive(GenericArchive::FileDetails* pDetails, + static bool SaveToArchive(GenericArchive::LocalFileDetails* pDetails, const uint8_t* dataBuf, long dataLen, const uint8_t* rsrcBuf, long rsrcLen, - CString& errMsg, CWnd* pDialog); + CString* pErrMsg, CWnd* pDialog); static const WCHAR kOpenNuFX[]; static const WCHAR kOpenBinaryII[]; diff --git a/app/NufxArchive.cpp b/app/NufxArchive.cpp index a00e9fa..31a0294 100644 --- a/app/NufxArchive.cpp +++ b/app/NufxArchive.cpp @@ -1156,51 +1156,57 @@ bail: } NuError NufxArchive::DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) + LocalFileDetails* pDetails) { NuError err; NuRecordIdx recordIdx = 0; - NuFileDetails nuFileDetails; - -retry: - nuFileDetails = *pDetails; // stuff class contents into struct - CStringA origNameA(pDetails->origName); - err = NuAddFile(fpArchive, origNameA /*pathname*/, - &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->origName); - } else if (err == kNuErrRecordExists) { - AddClashDialog dlg; + do { + // Must re-get the NuFileDetails, because updating the filename for + // rename will invalidate the previous version. + const NuFileDetails& nuFileDetails = pDetails->GetNuFileDetails(); - dlg.fWindowsName = pDetails->origName; - dlg.fStorageName = pDetails->storageName; - if (dlg.DoModal() != IDOK) { - err = kNuErrAborted; + // 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; } - if (dlg.fDoRename) { - LOGD("add clash: rename to '%ls'", (LPCWSTR) dlg.fNewName); - pDetails->storageName = dlg.fNewName; - goto retry; - } else { - LOGD("add clash: skip"); - err = kNuErrSkipped; - // fall through with error - } - } - //if (err != kNuErrNone) - // goto bail; + 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); -//bail: if (err != kNuErrNone && err != kNuErrAborted && err != kNuErrSkipped) { CString msg; msg.Format(L"Unable to add file '%ls': %hs.", - (LPCWSTR) pDetails->origName, NuStrError(err)); + (LPCWSTR) pDetails->GetLocalPathName(), NuStrError(err)); ShowFailureMsg(fpMsgWnd, msg, IDS_FAILED); } bail_quiet: @@ -1303,7 +1309,7 @@ NuResult NufxArchive::HandleReplaceExisting(const NuErrorStatus* pErrorStatus) ASSERT(pErrorStatus->canOverwrite); ASSERT(pErrorStatus->canSkip); ASSERT(pErrorStatus->canAbort); - ASSERT(!pErrorStatus->canRename); + ASSERT(!pErrorStatus->canRename); // TODO: remember why we can't rename /* no firm policy, ask the user */ ConfirmOverwriteDialog confOvwr; @@ -1313,8 +1319,7 @@ NuResult NufxArchive::HandleReplaceExisting(const NuErrorStatus* pErrorStatus) confOvwr.fExistingFileModWhen = DateTimeToSeconds(&pErrorStatus->pRecord->recModWhen); if (pErrorStatus->origPathname != NULL) { - // TODO: use wchar_t instead for origPathname - confOvwr.fNewFileSource = (char*) pErrorStatus->origPathname; + confOvwr.fNewFileSource = (WCHAR*) pErrorStatus->origPathname; PathName checkPath(confOvwr.fNewFileSource); confOvwr.fNewFileModWhen = checkPath.GetModWhen(); } else { @@ -1858,7 +1863,7 @@ GenericArchive::XferStatus NufxArchive::XferSelection(CWnd* pMsgWnd, * I think this now throws kXferCancelled whenever it's supposed to. Not * 100% sure, but it looks good. */ - LOGI("NufxArchive XferSelection!"); + LOGD("NufxArchive XferSelection!"); XferStatus retval = kXferFailed; unsigned char* dataBuf = NULL; unsigned char* rsrcBuf = NULL; @@ -1870,7 +1875,7 @@ GenericArchive::XferStatus NufxArchive::XferSelection(CWnd* pMsgWnd, for ( ; pSelEntry != NULL; pSelEntry = pSelSet->IterNext()) { long dataLen=-1, rsrcLen=-1; NufxEntry* pEntry = (NufxEntry*) pSelEntry->GetEntry(); - FileDetails fileDetails; + LocalFileDetails fileDetails; CString errMsg; ASSERT(dataBuf == NULL); @@ -1883,25 +1888,29 @@ GenericArchive::XferStatus NufxArchive::XferSelection(CWnd* pMsgWnd, continue; } - LOGI(" XFER converting '%ls'", pEntry->GetDisplayName()); + LOGD(" XFER converting '%ls'", pEntry->GetDisplayName()); - fileDetails.storageName = pEntry->GetDisplayName(); - fileDetails.fileType = pEntry->GetFileType(); - fileDetails.fileSysFmt = DiskImg::kFormatUnknown; - fileDetails.fileSysInfo = PathProposal::kDefaultStoredFssep; - fileDetails.access = pEntry->GetAccess(); - fileDetails.extraType = pEntry->GetAuxType(); - fileDetails.storageType = kNuStorageSeedling; + fileDetails.SetStrippedLocalPathName(pEntry->GetDisplayName()); + 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, &fileDetails.archiveWhen); + UNIXTimeToDateTime(&when, &nuwhen); + fileDetails.SetArchiveWhen(nuwhen); when = pEntry->GetModWhen(); - UNIXTimeToDateTime(&when, &fileDetails.modWhen); + UNIXTimeToDateTime(&when, &nuwhen); + fileDetails.SetModWhen(nuwhen); when = pEntry->GetCreateWhen(); - UNIXTimeToDateTime(&when, &fileDetails.createWhen); + UNIXTimeToDateTime(&when, &nuwhen); + fileDetails.SetCreateWhen(nuwhen); - pActionProgress->SetArcName(fileDetails.storageName); + pActionProgress->SetArcName(fileDetails.GetStrippedLocalPathName()); if (pActionProgress->SetProgress(0) == IDCANCEL) { retval = kXferCancelled; goto bail; @@ -1975,7 +1984,7 @@ GenericArchive::XferStatus NufxArchive::XferSelection(CWnd* pMsgWnd, goto bail; } - fileDetails.storageType = kNuStorageExtended; + fileDetails.SetStorageType(kNuStorageExtended); } else { ASSERT(rsrcBuf == NULL); } @@ -2026,22 +2035,23 @@ void NufxArchive::XferPrepare(const XferFileOptions* pXferOpts) (void) NuSetValue(fpArchive, kNuValueAllowDuplicates, true); } -CString NufxArchive::XferFile(FileDetails* pDetails, unsigned char** pDataBuf, - long dataLen, unsigned char** pRsrcBuf, long rsrcLen) +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->storageName); + 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->entryKind == FileDetails::kFileKindDirectory) { + if (pDetails->GetEntryKind() == LocalFileDetails::kFileKindDirectory) { delete[] *pDataBuf; delete[] *pRsrcBuf; *pDataBuf = *pRsrcBuf = NULL; @@ -2051,11 +2061,6 @@ CString NufxArchive::XferFile(FileDetails* pDetails, unsigned char** pDataBuf, ASSERT(dataLen >= 0 || rsrcLen >= 0); ASSERT(*pDataBuf != NULL || *pRsrcBuf != NULL); - /* add the record; we have "allow duplicates" enabled for clashes */ - NuRecordIdx recordIdx; - NuFileDetails nuFileDetails; - nuFileDetails = *pDetails; - /* * Odd bit of trivia: NufxLib refuses to accept an fssep of '\0'. It * really wants to have one. Which is annoying, since files coming @@ -2072,12 +2077,17 @@ CString NufxArchive::XferFile(FileDetails* pDetails, unsigned char** pDataBuf, * 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 (NuGetSepFromSysInfo(nuFileDetails.fileSysInfo) == 0) { - nuFileDetails.fileSysInfo = - NuSetSepInSysInfo(nuFileDetails.fileSysInfo, kNufxNoFssep); + 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) { @@ -2092,13 +2102,13 @@ CString NufxArchive::XferFile(FileDetails* pDetails, unsigned char** pDataBuf, ASSERT(*pDataBuf != NULL); /* strip the high ASCII from DOS and RDOS text files */ - if (pDetails->entryKind != FileDetails::kFileKindDiskImage && - pDetails->fileType == kFileTypeTXT && - DiskImg::UsesDOSFileStructure(pDetails->fileSysFmt)) + if (pDetails->GetEntryKind() != LocalFileDetails::kFileKindDiskImage && + pDetails->GetFileType() == kFileTypeTXT && + DiskImg::UsesDOSFileStructure(pDetails->GetFileSysFmt())) { LOGI(" Stripping high ASCII from '%ls'", - (LPCWSTR) pDetails->storageName); - unsigned char* ucp = *pDataBuf; + (LPCWSTR) pDetails->GetStrippedLocalPathName()); + uint8_t* ucp = *pDataBuf; long len = dataLen; while (len--) @@ -2117,7 +2127,7 @@ CString NufxArchive::XferFile(FileDetails* pDetails, unsigned char** pDataBuf, /* add the data fork, as a disk image if appropriate */ NuThreadID targetID; - if (pDetails->entryKind == FileDetails::kFileKindDiskImage) + if (pDetails->GetEntryKind() == LocalFileDetails::kFileKindDiskImage) targetID = kNuThreadIDDiskImage; else targetID = kNuThreadIDDataFork; diff --git a/app/NufxArchive.h b/app/NufxArchive.h index 45b98b0..1faa22c 100644 --- a/app/NufxArchive.h +++ b/app/NufxArchive.h @@ -171,7 +171,7 @@ private: CString* pErrMsg); virtual void XferPrepare(const XferFileOptions* pXferOpts) override; - virtual CString XferFile(FileDetails* pDetails, uint8_t** pDataBuf, + virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf, long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override; virtual void XferAbort(CWnd* pMsgWnd) override; virtual void XferFinish(CWnd* pMsgWnd) override; @@ -188,7 +188,7 @@ private: void AddFinish(void); virtual NuError DoAddFile(const AddFilesDialog* pAddOpts, - FileDetails* pDetails) override; + LocalFileDetails* pDetails) override; /* * Error handler callback for "bulk" adds. diff --git a/diskimg/DiskImg.cpp b/diskimg/DiskImg.cpp index c8e40f4..655f263 100644 --- a/diskimg/DiskImg.cpp +++ b/diskimg/DiskImg.cpp @@ -2468,7 +2468,7 @@ DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName */ dierr = ValidateCreateFormat(); if (dierr != kDIErrNone) { - LOGI("ERROR: CIC arg validation failed, bailing"); + LOGE("ERROR: CIC arg validation failed, bailing"); goto bail; } @@ -2486,7 +2486,7 @@ DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName fd = open(pathName, O_CREAT | O_EXCL, 0644); if (fd < 0) { dierr = (DIError) errno; - LOGI("ERROR: unable to create file '%s' (errno=%d)", + LOGE("ERROR: unable to create file '%s' (errno=%d)", pathName, dierr); goto bail; } @@ -2627,7 +2627,7 @@ DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName } if (fpImageWrapper == NULL) { - LOGI(" DI couldn't figure out the file format"); + LOGW(" DI couldn't figure out the file format"); dierr = kDIErrUnrecognizedFileFmt; goto bail; } @@ -2637,7 +2637,7 @@ DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName dierr = fpImageWrapper->Create(fLength, fPhysical, fOrder, fDOSVolumeNum, fpWrapperGFD, &fWrappedLength, &fpDataGFD); if (dierr != kDIErrNone) { - LOGI("ImageWrapper Create failed, err=%d", dierr); + LOGE("ImageWrapper Create failed, err=%d", dierr); goto bail; } assert(fpDataGFD != NULL); @@ -2658,7 +2658,7 @@ DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName assert(!skipFormat); // don't skip low-level nibble formatting! if (fDOSVolumeNum == kVolumeNumNotSet) { fDOSVolumeNum = kDefaultNibbleVolumeNum; - LOGI(" Using default nibble volume num"); + LOGD(" Using default nibble volume num"); } dierr = FormatNibbles(fpDataGFD); // write basic nibble stuff @@ -2697,7 +2697,7 @@ DIError DiskImg::ValidateCreateFormat(void) const */ if (fHasBlocks && fNumBlocks >= 4194304) { // 2GB or larger? if (fFileFormat != kFileFormatUnadorned) { - LOGI("CreateImage: images >= 2GB can only be unadorned"); + LOGW("CreateImage: images >= 2GB can only be unadorned"); return kDIErrInvalidCreateReq; } } @@ -2707,14 +2707,14 @@ DIError DiskImg::ValidateCreateFormat(void) const fOrder == kSectorOrderUnknown || fFormat == kFormatUnknown) { - LOGI("CreateImage: ambiguous format"); + LOGW("CreateImage: ambiguous format"); return kDIErrInvalidCreateReq; } if (fOuterFormat != kOuterFormatNone && fOuterFormat != kOuterFormatGzip && fOuterFormat != kOuterFormatZip) { - LOGI("CreateImage: unsupported outer format %d", fOuterFormat); + LOGW("CreateImage: unsupported outer format %d", fOuterFormat); return kDIErrInvalidCreateReq; } if (fFileFormat != kFileFormatUnadorned && @@ -2726,7 +2726,7 @@ DIError DiskImg::ValidateCreateFormat(void) const fFileFormat != kFileFormatNuFX && fFileFormat != kFileFormatDDD) { - LOGI("CreateImage: unsupported file format %d", fFileFormat); + LOGW("CreateImage: unsupported file format %d", fFileFormat); return kDIErrInvalidCreateReq; } if (fFormat != kFormatGenericPhysicalOrd && @@ -2734,7 +2734,7 @@ DIError DiskImg::ValidateCreateFormat(void) const fFormat != kFormatGenericDOSOrd && fFormat != kFormatGenericCPMOrd) { - LOGI("CreateImage: may only use 'generic' formats"); + LOGW("CreateImage: may only use 'generic' formats"); return kDIErrInvalidCreateReq; } @@ -2743,25 +2743,25 @@ DIError DiskImg::ValidateCreateFormat(void) const */ if (fPhysical != kPhysicalFormatSectors) { if (fOrder != kSectorOrderPhysical) { - LOGI("CreateImage: nibble images are always 'physical' order"); + LOGW("CreateImage: nibble images are always 'physical' order"); return kDIErrInvalidCreateReq; } if (GetHasSectors() == false && GetHasNibbles() == false) { - LOGI("CreateImage: must set hasSectors(%d) or hasNibbles(%d)", + LOGW("CreateImage: must set hasSectors(%d) or hasNibbles(%d)", GetHasSectors(), GetHasNibbles()); return kDIErrInvalidCreateReq; } if (fpNibbleDescr == NULL && GetNumSectPerTrack() > 0) { - LOGI("CreateImage: must provide NibbleDescr for non-sector"); + LOGW("CreateImage: must provide NibbleDescr for non-sector"); return kDIErrInvalidCreateReq; } if (fpNibbleDescr != NULL && fpNibbleDescr->numSectors != GetNumSectPerTrack()) { - LOGI("CreateImage: ?? nd->numSectors=%d, GetNumSectPerTrack=%d", + LOGW("CreateImage: ?? nd->numSectors=%d, GetNumSectPerTrack=%d", fpNibbleDescr->numSectors, GetNumSectPerTrack()); return kDIErrInvalidCreateReq; } @@ -2773,14 +2773,14 @@ DIError DiskImg::ValidateCreateFormat(void) const fpNibbleDescr->encoding != kNibbleEnc62)) ) { - LOGI("CreateImage: sector count/encoding mismatch"); + LOGW("CreateImage: sector count/encoding mismatch"); return kDIErrInvalidCreateReq; } if (GetNumTracks() != kTrackCount525 && !(GetNumTracks() == 40 && fFileFormat == kFileFormatTrackStar)) { - LOGI("CreateImage: unexpected track count %ld", GetNumTracks()); + LOGW("CreateImage: unexpected track count %ld", GetNumTracks()); return kDIErrInvalidCreateReq; } } @@ -2788,7 +2788,7 @@ DIError DiskImg::ValidateCreateFormat(void) const if (fPhysical != kPhysicalFormatSectors && fPhysical != kPhysicalFormatNib525_6656) { - LOGI("CreateImage: 2MG can't handle physical %d", fPhysical); + LOGW("CreateImage: 2MG can't handle physical %d", fPhysical); return kDIErrInvalidCreateReq; } @@ -2796,78 +2796,78 @@ DIError DiskImg::ValidateCreateFormat(void) const (fOrder != kSectorOrderProDOS && fOrder != kSectorOrderDOS)) { - LOGI("CreateImage: 2MG requires DOS or ProDOS ordering"); + LOGW("CreateImage: 2MG requires DOS or ProDOS ordering"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatNuFX) { if (fOuterFormat != kOuterFormatNone) { - LOGI("CreateImage: can't mix NuFX and outer wrapper"); + LOGW("CreateImage: can't mix NuFX and outer wrapper"); return kDIErrInvalidCreateReq; } if (fPhysical != kPhysicalFormatSectors) { - LOGI("CreateImage: NuFX physical must be sectors"); + LOGW("CreateImage: NuFX physical must be sectors"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderProDOS) { - LOGI("CreateImage: NuFX is always ProDOS-order"); + LOGW("CreateImage: NuFX is always ProDOS-order"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatDiskCopy42) { if (fPhysical != kPhysicalFormatSectors) { - LOGI("CreateImage: DC42 physical must be sectors"); + LOGW("CreateImage: DC42 physical must be sectors"); return kDIErrInvalidCreateReq; } if ((GetHasBlocks() && GetNumBlocks() != 1600) || (GetHasSectors() && (GetNumTracks() != 200 || GetNumSectPerTrack() != 16))) { - LOGI("CreateImage: DC42 only for 800K disks"); + LOGW("CreateImage: DC42 only for 800K disks"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderProDOS && fOrder != kSectorOrderDOS) // used for UNIDOS disks?? { - LOGI("CreateImage: DC42 is always ProDOS or DOS"); + LOGW("CreateImage: DC42 is always ProDOS or DOS"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatSim2eHDV) { if (fPhysical != kPhysicalFormatSectors) { - LOGI("CreateImage: Sim2eHDV physical must be sectors"); + LOGW("CreateImage: Sim2eHDV physical must be sectors"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderProDOS) { - LOGI("CreateImage: Sim2eHDV is always ProDOS-order"); + LOGW("CreateImage: Sim2eHDV is always ProDOS-order"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatTrackStar) { if (fPhysical != kPhysicalFormatNib525_Var) { - LOGI("CreateImage: TrackStar physical must be var-nibbles"); + LOGW("CreateImage: TrackStar physical must be var-nibbles"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatFDI) { if (fPhysical != kPhysicalFormatNib525_Var) { - LOGI("CreateImage: FDI physical must be var-nibbles"); + LOGW("CreateImage: FDI physical must be var-nibbles"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatDDD) { if (fPhysical != kPhysicalFormatSectors) { - LOGI("CreateImage: DDD physical must be sectors"); + LOGW("CreateImage: DDD physical must be sectors"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderDOS) { - LOGI("CreateImage: DDD is always DOS-order"); + LOGW("CreateImage: DDD is always DOS-order"); return kDIErrInvalidCreateReq; } if (!GetHasSectors() || GetNumTracks() != 35 || GetNumSectPerTrack() != 16) { - LOGI("CreateImage: DDD is only for 16-sector 35-track disks"); + LOGW("CreateImage: DDD is only for 16-sector 35-track disks"); return kDIErrInvalidCreateReq; } } diff --git a/diskimg/DiskImg.h b/diskimg/DiskImg.h index 3a25d98..de65051 100644 --- a/diskimg/DiskImg.h +++ b/diskimg/DiskImg.h @@ -1159,6 +1159,13 @@ public: * normalized string. If the buffer is NULL or isn't big enough, no part * of the normalized path will be copied into the buffer, and a specific * error (kDIErrDataOverrun) will be returned. + * + * Generally speaking, the normalization process involves separating + * the pathname into its components, modifying each filename as needed + * to make it legal on the current filesystem, and then reassembling + * the components. + * + * The input and output strings are Mac OS Roman. */ virtual DIError NormalizePath(const char* path, char fssep, char* normalizedBuf, int* pNormalizedBufLen) @@ -1184,9 +1191,9 @@ public: const char* pathName; // full pathname for file on disk image char fssep; int storageType; // determines normal, subdir, or forked - long fileType; - long auxType; - int access; + uint32_t fileType; + uint32_t auxType; + uint32_t access; time_t createWhen; time_t modWhen; } CreateParms; @@ -1459,10 +1466,13 @@ public: * NOTE: there is no guarantee that GetPathName will return unique values * (duplicates are possible). We don't guarantee that you won't get an * empty string back (it's valid to have an empty filename in the dir in - * DOS 3.3, and it's possible for other filesystems to be damaged). The - * pathname may receive some minor sanitizing, e.g. removal or conversion - * of high ASCII and control characters, but some filesystems (like HFS) - * make use of them. + * DOS 3.3, and it's possible for other filesystems to be damaged). + * + * The filename returned is defined to be null-terminated Mac OS Roman. + * For most filesystems this is automatic, as filenames are restricted + * to ASCII, but for DOS 3.3 it requires stripping high bits. It also + * means that embedded nulls in HFS filenames (which are discouraged but + * allowed) will be lost. * * We do guarantee that the contents of subdirectories are grouped * together. This makes it much easier to construct a hierarchy out of @@ -1474,7 +1484,7 @@ public: virtual char GetFssep(void) const = 0; // '\0' if none virtual uint32_t GetFileType(void) const = 0; virtual uint32_t GetAuxType(void) const = 0; - virtual uint32_t GetAccess(void) const = 0; // ProDOS-style perms + virtual uint32_t GetAccess(void) const = 0; // ProDOS-style perms virtual time_t GetCreateWhen(void) const = 0; virtual time_t GetModWhen(void) const = 0; virtual di_off_t GetDataLength(void) const = 0; // len of data fork diff --git a/diskimg/DiskImgDetail.h b/diskimg/DiskImgDetail.h index 6c3d380..122c461 100644 --- a/diskimg/DiskImgDetail.h +++ b/diskimg/DiskImgDetail.h @@ -1407,10 +1407,6 @@ private: /* * Holds DOS files. Works for DOS33, DOS32, and "wide" DOS implementations. * - * Catalog contents are public so anyone who wants gory details of DOS 3.3 - * stuff can poke at whatever they want. Anybody else can use the virtual - * interfaces to get standardized answers for things like file type. - * * The embedded address and length fields found in Applesoft, Integer, and * Binary files are quietly skipped over with the fDataOffset field when * files are read. @@ -1470,6 +1466,10 @@ public: void Dump(void) const; + friend class DiskFSDOS33; + friend class A2FDDOS; + +private: typedef DiskFSDOS33::TrackSector TrackSector; /* @@ -1490,7 +1490,7 @@ public: // these are computed or determined from the file contents uint16_t fAuxType; // addr for bin, etc. - short fDataOffset; // for 'A'/'B'/'I' with embedded len + uint16_t fDataOffset; // 0/2/4, for 'A'/'B'/'I' with embedded len di_off_t fLength; // file length, in bytes di_off_t fSparseLength; // file length, factoring sparse out @@ -1503,7 +1503,6 @@ public: static void MakeDOSName(char* buf, const char* name); static void TrimTrailingSpaces(char* filename); -private: DIError ExtractTSPairs(const uint8_t* sctBuf, TrackSector* tsList, int* pLastNonZero); diff --git a/diskimg/GenericFD.cpp b/diskimg/GenericFD.cpp index b751cfb..a0adccc 100644 --- a/diskimg/GenericFD.cpp +++ b/diskimg/GenericFD.cpp @@ -254,11 +254,16 @@ DIError GFDFile::Open(const char* filename, bool readOnly) fFd = open(filename, readOnly ? O_RDONLY|O_BINARY : O_RDWR|O_BINARY, 0); if (fFd < 0) { - if (errno == EACCES) + if (errno == EACCES) { dierr = kDIErrAccessDenied; - else + } else if (errno == EINVAL) { + // Happens on Win32 if a Unicode filename conversion failed, + // because non-converting chars turn into '?', which is illegal. + dierr = kDIErrInvalidArg; + } else { dierr = ErrnoOrGeneric(); - LOGI(" GDFile Open failed opening '%s', ro=%d (err=%d)", + } + LOGW(" GDFile Open failed opening '%s', ro=%d (err=%d)", filename, readOnly, dierr); return dierr; } @@ -278,7 +283,7 @@ DIError GFDFile::Read(void* buf, size_t length, size_t* pActual) return kDIErrEOF; if (actual < 0) { dierr = ErrnoOrGeneric(); - LOGI(" GDFile Read failed on %d bytes (actual=%d, err=%d)", + LOGW(" GDFile Read failed on %d bytes (actual=%d, err=%d)", length, actual, dierr); return dierr; } diff --git a/nufxlib/FileIO.c b/nufxlib/FileIO.c index c4c11db..9d20366 100644 --- a/nufxlib/FileIO.c +++ b/nufxlib/FileIO.c @@ -1097,15 +1097,16 @@ NuError Nu_CloseOutputFile(NuArchive* pArchive, const NuRecord* pRecord, err = Nu_SetFileDates(pArchive, pRecord, pathnameUNI); BailError(err); - err = Nu_SetFileAccess(pArchive, pRecord, pathnameUNI); - BailError(err); - #if defined(MAC_LIKE) /* could also do this earlier and pass the fd for fsetxattr */ + /* NOTE: must do this before Nu_SetFileAccess */ err = Nu_SetFinderInfo(pArchive, pRecord, pathnameUNI); BailError(err); #endif + err = Nu_SetFileAccess(pArchive, pRecord, pathnameUNI); + BailError(err); + bail: return kNuErrNone; } diff --git a/nufxlib/NufxLibPriv.h b/nufxlib/NufxLibPriv.h index dd3df95..c18a4b7 100644 --- a/nufxlib/NufxLibPriv.h +++ b/nufxlib/NufxLibPriv.h @@ -429,25 +429,25 @@ union NuDataSink { * the first arguments to Nu_ReportError, so we don't have to type them * in every time we use the function. It would've been much easier to * use a gcc-style varargs macro, but not everybody uses gcc. + * + * TODO: Visual C++ has vararg macros now; time to replace this. */ -#ifdef DEBUG_MSGS - #ifdef HAS__FUNCTION__ - #define _FUNCTION_ __FUNCTION__ - #else - #define _FUNCTION_ "" - #endif +#ifdef HAS__FUNCTION__ +# define _FUNCTION_ __FUNCTION__ +#else +# define _FUNCTION_ "" +#endif - #define NU_BLOB pArchive, __FILE__, __LINE__, _FUNCTION_, false - #define NU_BLOB_DEBUG pArchive, __FILE__, __LINE__, _FUNCTION_, true - #define NU_NILBLOB NULL, __FILE__, __LINE__, _FUNCTION_, false - #define DebugShowError(err) \ +#define NU_BLOB pArchive, __FILE__, __LINE__, _FUNCTION_, false +#define NU_BLOB_DEBUG pArchive, __FILE__, __LINE__, _FUNCTION_, true +#define NU_NILBLOB NULL, __FILE__, __LINE__, _FUNCTION_, false + +#ifdef DEBUG_MSGS +# define DebugShowError(err) \ Nu_ReportError(pArchive, __FILE__, __LINE__, _FUNCTION_, \ true, err, "(DEBUG)"); #else - #define NU_BLOB pArchive, "", 0, "", false - #define NU_BLOB_DEBUG pArchive, "", 0, "", true - #define NU_NILBLOB NULL, "", 0, "", false - #define DebugShowError(err) ((void)0) +# define DebugShowError(err) ((void)0) #endif /* diff --git a/util/MyDebug.cpp b/util/MyDebug.cpp index 92df5fd..0088672 100644 --- a/util/MyDebug.cpp +++ b/util/MyDebug.cpp @@ -76,8 +76,24 @@ void DebugLog::Log(LogSeverity severity, const char* file, int line, va_list argptr; char textBuf[4096]; + // TODO: _vsnprintf() doesn't deal with "%ls" well. If it encounters a + // character it can't translate, it stops and returns -1. This is + // very annoying if you're trying to print a wide-char filename that + // includes characters that aren't in CP-1252. + // + // We can probably fix this by converting "format" to a wide string, + // printing with _vsnwprintf(), and then converting the output back + // to narrow manually (selecting options that prevent it from stopping + // mid-string on failure). As an optimization, we only need to do this + // if the format string includes a "%ls" specifier. + // + // The interpretation of plain "%s" will change if we use a wide printf + // function (it's a Microsoft extension, not ANSI behavior), so we'd also + // want to check for "%s" and complain if we see it. All callers must + // use explicit "%hs" or "%ls". + va_start(argptr, format); - _vsnprintf(textBuf, NELEM(textBuf) - 1, format, argptr); + (void) _vsnprintf(textBuf, NELEM(textBuf) - 1, format, argptr); va_end(argptr); textBuf[NELEM(textBuf) - 1] = '\0'; diff --git a/util/PathName.h b/util/PathName.h index e1e38a2..d58e7dc 100644 --- a/util/PathName.h +++ b/util/PathName.h @@ -127,6 +127,30 @@ public: */ static const WCHAR* FilenameOnly(const WCHAR* pathname, WCHAR fssep); + /* + * Test to see if a wide-to-narrow filename conversion failed. + * + * Returns true if all is well, false with *pErrMsg set if something + * went wrong. + */ + static bool TestNarrowConversion(const CString& original, + const CStringA& converted, CString* pErrMsg) { + int index = converted.ReverseFind('?'); + if (index < 0) { + // no '?' is good + } else if (index == 2 && converted.Left(4) == "\\\\?\\") { + // Win32 file namespace path strings start with "\\?\". If that's + // the first occurrence of '?', we're still good. + } else { + // This is most likely the result of a failed wide-to-narrow + // string conversion. + pErrMsg->Format(L"Unable to open '%ls' -- Unicode filename " + L"conversion is invalid ('%hs')", + (LPCWSTR) original, (LPCSTR) converted); + return false; + } + return true; + } private: DECLARE_COPY_AND_OPEQ(PathName)