ciderpress/app/DiskArchive.h
Andy McFadden b79498da50 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.
2015-01-08 14:16:20 -08:00

417 lines
14 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Disk image "archive" support.
*/
#ifndef APP_DISKARCHIVE_H
#define APP_DISKARCHIVE_H
#include "GenericArchive.h"
#include "../diskimg/DiskImg.h"
class RenameEntryDialog;
/*
* One file in a disk image.
*/
class DiskEntry : public GenericEntry {
public:
DiskEntry(A2File* pFile) : fpFile(pFile)
{}
virtual ~DiskEntry(void) {}
virtual int ExtractThreadToBuffer(int which, char** ppText, long* pLength,
CString* pErrMsg) const override;
virtual int ExtractThreadToFile(int which, FILE* outfp, ConvertEOL conv,
ConvertHighASCII convHA, CString* pErrMsg) const override;
virtual long GetSelectionSerial(void) const override
{ return -1; } // idea: T/S block number
/*
* Figure out whether or not we're allowed to change a file's type and
* aux type.
*/
virtual bool GetFeatureFlag(Feature feature) const override;
// return the underlying FS format for this file
virtual DiskImg::FSFormat GetFSFormat(void) const {
ASSERT(fpFile != NULL);
return fpFile->GetFSFormat();
}
A2File* GetA2File(void) const { return fpFile; }
void SetA2File(A2File* pFile) { fpFile = pFile; }
private:
/*
* Copy data from the open A2File to outfp, possibly converting EOL along
* the way.
*/
DIError CopyData(A2FileDescr* pOpenFile, FILE* outfp, ConvertEOL conv,
ConvertHighASCII convHA, CString* pMsg) const;
A2File* fpFile;
};
/*
* Disk image add-ons to GenericArchive.
*/
class DiskArchive : public GenericArchive {
public:
DiskArchive(void) : fpPrimaryDiskFS(NULL), fIsReadOnly(false),
fpAddDataHead(NULL), fpAddDataTail(NULL)
{}
virtual ~DiskArchive(void) { (void) Close(); }
/* pass this as the "options" value to the New() function */
typedef struct {
DiskImgLib::DiskImg::FSFormat format;
DiskImgLib::DiskImg::SectorOrder sectorOrder;
} NewOptionsBase;
typedef union {
NewOptionsBase base;
struct {
NewOptionsBase base;
long numBlocks;
} blank;
struct {
NewOptionsBase base;
const WCHAR* volName;
long numBlocks;
} prodos;
struct {
NewOptionsBase base;
const WCHAR* volName;
long numBlocks;
} pascalfs; // "pascal" is reserved token in MSVC++
struct {
NewOptionsBase base;
const WCHAR* volName;
long numBlocks;
} hfs;
struct {
NewOptionsBase base;
int volumeNum;
long numTracks;
int numSectors;
bool allocDOSTracks;
} dos;
} NewOptions;
/*
* Perform one-time initialization of the DiskLib library.
*/
static CString AppInit(void);
/*
* Perform one-time cleanup of DiskImgLib at shutdown time.
*/
static void AppCleanup(void);
/*
* Finish instantiating a DiskArchive object by opening an existing file.
*/
virtual OpenResult Open(const WCHAR* filename, bool readOnly,
CString* pErrMsg) override;
/*
* Finish instantiating a DiskArchive object by creating a new archive.
*
* Returns an error string on failure, or "" on success.
*/
virtual CString New(const WCHAR* filename, const void* options) override;
/*
* Flush the DiskArchive object.
*
* Most of the stuff we do with disk images goes straight through, but in
* the case of compressed disks we don't normally re-compress them until
* it's time to close them. This forces us to update the copy on disk.
*
* Returns an empty string on success, or an error message on failure.
*/
virtual CString Flush(void) override;
/*
* Reload the stuff from the underlying DiskFS.
*
* This also does a "lite" flush of the disk data. For files that are
* essentially being written as we go, this does little more than clear
* the "dirty" flag. Files that need to be recompressed or have some
* other slow operation remain dirty.
*
* We don't need to do the flush as part of the reload -- we can load the
* contents with everything in a perfectly dirty state. We don't need to
* do it at all. We do it to keep the "dirty" flag clear when nothing is
* really dirty, and we do it here because almost all of our functions call
* "reload" after making changes, which makes it convenient to call from here.
*/
virtual CString Reload(void) override;
/*
* Returns true if the archive has un-flushed modifications pending.
*/
virtual bool IsModified(void) const override;
/*
* Return an description of the disk archive, suitable for display in the
* main title bar.
*/
virtual void GetDescription(CString* pStr) const override;
virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) override;
virtual bool AddDisk(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) override
{ ASSERT(false); return false; }
virtual bool CreateSubdir(CWnd* pMsgWnd, GenericEntry* pParentEntry,
const WCHAR* newName) override;
virtual bool TestSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override
{ ASSERT(false); return false; }
virtual bool DeleteSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override;
virtual bool RenameSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override;
virtual CString TestPathName(const GenericEntry* pGenericEntry,
const CString& basePath, const CString& newName,
char newFssep) const override;
virtual bool RenameVolume(CWnd* pMsgWnd, DiskFS* pDiskFS,
const WCHAR* newName) override;
virtual CString TestVolumeName(const DiskFS* pDiskFS,
const WCHAR* newName) const override;
virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
const RecompressOptionsDialog* pRecompOpts) override
{ ASSERT(false); return false; }
virtual bool GetComment(CWnd* pMsgWnd, const GenericEntry* pEntry,
CString* pStr) override
{ ASSERT(false); return false; }
virtual bool SetComment(CWnd* pMsgWnd, GenericEntry* pEntry,
const CString& str) override
{ ASSERT(false); return false; }
virtual bool DeleteComment(CWnd* pMsgWnd, GenericEntry* pEntry) override
{ ASSERT(false); return false; }
virtual bool SetProps(CWnd* pMsgWnd, GenericEntry* pEntry,
const FileProps* pProps) override;
/*
* User has updated their preferences. Take note.
*
* Setting preferences in a DiskFS causes those prefs to be pushed down
* to all sub-volumes.
*/
virtual void PreferencesChanged(void) override;
virtual long GetCapability(Capability cap) override;
virtual XferStatus XferSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
ActionProgressDialog* pActionProgress,
const XferFileOptions* pXferOpts) override;
virtual bool IsReadOnly(void) const { return fIsReadOnly; }
const DiskImg* GetDiskImg(void) const { return &fDiskImg; }
DiskFS* GetDiskFS(void) const { return fpPrimaryDiskFS; }
/*
* Progress update callback, called from DiskImgLib during read/write
* operations.
*
* Returns "true" if we should continue;
*/
static bool ProgressCallback(DiskImgLib::A2FileDescr* pFile,
DiskImgLib::di_off_t max, DiskImgLib::di_off_t current, void* state);
private:
/*
* Close the DiskArchive ojbect.
*/
virtual CString Close(void);
virtual void XferPrepare(const XferFileOptions* pXferOpts) override;
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;
/*
* Progress update callback, called from DiskImgLib while scanning a volume
* during Open().
*
* "str" must not contain a '%'. (TODO: fix that)
*
* Returns "true" if we should continue.
*/
static bool ScanProgressCallback(void* cookie, const char* str,
int count);
/*
* Internal class used to keep track of files we're adding.
*/
class FileAddData {
public:
FileAddData(const LocalFileDetails* pDetails, char* fsNormalPathMOR) {
fDetails = *pDetails;
fFSNormalPathMOR = fsNormalPathMOR;
fpOtherFork = NULL;
fpNext = NULL;
}
virtual ~FileAddData(void) {}
FileAddData* GetNext(void) const { return fpNext; }
void SetNext(FileAddData* pNext) { fpNext = pNext; }
FileAddData* GetOtherFork(void) const { return fpOtherFork; }
void SetOtherFork(FileAddData* pData) { fpOtherFork = pData; }
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 Mac OS Roman string.
*/
const char* GetFSNormalPath(void) const { return fFSNormalPathMOR; }
private:
LocalFileDetails fDetails;
// 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;
};
virtual ArchiveKind GetArchiveKind(void) override { return kArchiveDiskImage; }
virtual NuError DoAddFile(const AddFilesDialog* pAddOpts,
LocalFileDetails* pDetails) override;
/*
* Reload the contents of the archive, showing an error message if the
* reload fails.
*
* Returns 0 on success, -1 on failure.
*/
int InternalReload(CWnd* pMsgWnd);
/*
* Compare DiskEntry display names in descending order (Z-A).
*/
static int CompareDisplayNamesDesc(const void* ventry1, const void* ventry2);
/*
* Load the contents of a "disk archive". Returns 0 on success.
*/
int LoadContents(void);
/*
* Load the contents of a DiskFS.
*
* Recursively handle sub-volumes. "volName" holds the name of the
* sub-volume as it should appear in the list.
*/
int LoadDiskFSContents(DiskFS* pDiskFS, const WCHAR* volName);
void DowncaseSubstring(CString* pStr, int startPos, int endPos,
bool prevWasSpace);
/*
* Handle a debug message from the DiskImg library.
*/
static void DebugMsgHandler(const char* file, int line, const char* msg);
/*
* A file we're adding clashes with an existing file. Decide what to do
* about it.
*
* Returns one of the following:
* kNuOverwrite - overwrite the existing file
* kNuSkip - skip adding the existing file
* kNuRename - user wants to rename the file
* kNuAbort - cancel out of the entire add process
*
* Side effects:
* Sets fOverwriteExisting and fOverwriteNoAsk if a "to all" button is hit
* Replaces pDetails->storageName if the user elects to rename
*/
NuResult HandleReplaceExisting(const A2File* pExisting,
LocalFileDetails* pDetails);
/*
* Process the list of pending file adds.
*
* This is where the rubber (finally!) meets the road.
*/
CString ProcessFileAddData(DiskFS* pDiskFS, int addOptsConvEOL);
/*
* Load a file into a buffer, possibly converting EOL markers and setting
* "high ASCII" along the way.
*
* Returns a pointer to a newly-allocated buffer (new[]) and the data length.
* If the file is empty, no buffer will be allocated.
*
* Returns an empty string on success, or an error message on failure.
*/
CString LoadFile(const WCHAR* pathName, uint8_t** pBuf, long* pLen,
GenericEntry::ConvertEOL conv, GenericEntry::ConvertHighASCII convHA) const;
/*
* Add a file with the supplied data to the disk image.
*
* Forks that exist but are empty have a length of zero. Forks that don't
* exist have a length of -1.
*
* Called by XferFile and ProcessFileAddData.
*/
DIError AddForksToDisk(DiskFS* pDiskFS, const DiskFS::CreateParms* pParms,
const uint8_t* dataBuf, long dataLen,
const uint8_t* rsrcBuf, long rsrcLen) const;
/*
* Add an entry to the end of the FileAddData list.
*
* If "storageName" (the Windows filename with type goodies stripped, but
* without filesystem normalization) matches an entry already in the list,
* we check to see if these are forks of the same file. If they are
* different forks and we don't already have both forks, we put the
* pointer into the "fork pointer" of the existing file rather than adding
* it to the end of the list.
*/
void AddToAddDataList(FileAddData* pData);
/*
* Free all entries in the FileAddData list.
*/
void FreeAddDataList(void);
/*
* Set up a RenameEntryDialog for the entry in "*pEntry".
*
* Returns true on success, false on failure.
*/
bool SetRenameFields(CWnd* pMsgWnd, DiskEntry* pEntry,
RenameEntryDialog* pDialog);
DiskImg fDiskImg; // DiskImg object for entire disk
DiskFS* fpPrimaryDiskFS; // outermost DiskFS
bool fIsReadOnly;
/* active state while adding files */
FileAddData* fpAddDataHead;
FileAddData* fpAddDataTail;
bool fOverwriteExisting;
bool fOverwriteNoAsk;
/* state during xfer */
//CString fXferStoragePrefix;
DiskFS* fpXferTargetFS;
};
#endif /*APP_DISKARCHIVE_H*/