ciderpress/app/GenericArchive.h

707 lines
25 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Generic Apple II archive handling.
*
* These are abstract base classes.
*/
#ifndef APP_GENERICARCHIVE_H
#define APP_GENERICARCHIVE_H
#include "Preferences.h"
#include "../util/UtilLib.h"
#include "../diskimg/DiskImg.h"
#include "../nufxlib/NufxLib.h"
#include "../reformat/Reformat.h"
#include <time.h>
#include <string.h>
// this shouldn't be in a header file, but in here it's probably okay
using namespace DiskImgLib;
class ActionProgressDialog;
class AddFilesDialog;
class RecompressOptionsDialog;
class SelectionSet;
struct Win32dirent;
class GenericArchive;
const int kFileTypeTXT = 0x04;
const int kFileTypeBIN = 0x06;
const int kFileTypeSRC = 0xb0;
const int kFileTypeINT = 0xfa;
const int kFileTypeBAS = 0xfc;
/*
* Set of data allowed in file property "set file info" calls.
*/
typedef struct FileProps {
unsigned long fileType;
unsigned long auxType;
unsigned long access;
time_t createWhen;
time_t modWhen;
} FileProps;
/*
* Options for converting between file archives and disk archives.
*/
class XferFileOptions {
public:
XferFileOptions(void) :
fTarget(nil), fPreserveEmptyFolders(false), fpTargetFS(nil)
{}
~XferFileOptions(void) {}
/* where the stuff is going */
GenericArchive* fTarget;
/* these really only have meaning when converting disk to file */
//bool fConvDOSText;
//bool fConvPascalText;
bool fPreserveEmptyFolders;
/* only useful when converting files to a disk image */
//CString fStoragePrefix;
DiskImgLib::DiskFS* fpTargetFS;
};
/*
* Generic description of an Apple II file.
*
* Everything returned by the basic "get" calls for display in the ContentList
* must be held in local storage (i.e. not just pointers into DiskFS data).
* Otherwise, we run the risk of doing some DiskFS updates and having weird
* things happen in the ContentList.
*/
class GenericEntry {
public:
GenericEntry(void);
virtual ~GenericEntry(void);
/* kinds of files found in archives */
enum RecordKind {
kRecordKindUnknown = 0,
kRecordKindDisk,
kRecordKindFile,
kRecordKindForkedFile,
kRecordKindDirectory,
kRecordKindVolumeDir,
};
/*
* Threads we will view or extract (threadMask). This is no longer used
* for viewing files, but still plays a role when extracting.
*/
enum {
// create one entry for each matching thread
kDataThread = 0x01,
kRsrcThread = 0x02,
kDiskImageThread = 0x04,
kCommentThread = 0x08,
// grab any of the above threads
kAnyThread = 0x10,
// set this if we allow matches on directory entries
kAllowDirectory = 0x20,
// and volume directory entries
kAllowVolumeDir = 0x40,
// set to include "damaged" files
kAllowDamaged = 0x80,
};
/* EOL conversion mode for threads being extracted */
typedef enum ConvertEOL {
kConvertUnknown = 0, kConvertEOLOff, kConvertEOLOn, kConvertEOLAuto
} ConvertEOL;
typedef enum EOLType {
kEOLUnknown = 0, kEOLCR, kEOLLF, kEOLCRLF
};
/* high ASCII conversion mode for threads being extracted */
typedef enum ConvertHighASCII {
kConvertHAUnknown = 0, kConvertHAOff, kConvertHAOn, kConvertHAAuto
} ConvertHighASCII;
/* ProDOS access flags, used for all filesystems */
enum {
kAccessRead = 0x01,
kAccessWrite = 0x02,
kAccessInvisible = 0x04,
kAccessBackup = 0x20,
kAccessRename = 0x40,
kAccessDelete = 0x80
};
/* features supported by underlying archive */
typedef enum Feature {
kFeatureCanChangeType,
kFeaturePascalTypes,
kFeatureDOSTypes,
kFeatureHFSTypes,
kFeatureHasFullAccess,
kFeatureHasSimpleAccess, // mutually exclusive with FullAccess
kFeatureHasInvisibleFlag,
} Feature;
// retrieve thread (or filesystem) data
virtual int ExtractThreadToBuffer(int which, char** ppText, long* pLength,
CString* pErrMsg) const = 0;
virtual int ExtractThreadToFile(int which, FILE* outfp, ConvertEOL conv,
ConvertHighASCII convHA, CString* pErrMsg) const = 0;
// This helps us retain the ContentList selection across a Reload(). Only
// necessary for read-write archives, since those are the only ones that
// ever need to be reloaded. Value must be nonzero to be used.
virtual long GetSelectionSerial(void) const = 0;
/* are we allowed to change the file/aux type of this entry? */
/* (may need to generalize this to "changeable attrs" bitmask) */
virtual bool GetFeatureFlag(Feature feature) const = 0;
long GetIndex(void) const { return fIndex; }
void SetIndex(long idx) { fIndex = idx; }
const WCHAR* GetPathName(void) const { return fPathName; }
void SetPathName(const WCHAR* path);
const WCHAR* GetFileName(void);
const WCHAR* GetFileNameExtension(void); // returns e.g. ".SHK"
CStringA GetFileNameExtensionA(void);
void SetSubVolName(const WCHAR* name);
const WCHAR* GetSubVolName(void) const { return fSubVolName; }
const WCHAR* GetDisplayName(void) const; // not really "const"
char GetFssep(void) const { return fFssep; }
void SetFssep(char fssep) { fFssep = fssep; }
long GetFileType(void) const { return fFileType; }
void SetFileType(long type) { fFileType = type; }
long GetAuxType(void) const { return fAuxType; }
void SetAuxType(long type) { fAuxType = type; }
long GetAccess(void) const { return fAccess; }
void SetAccess(long access) { fAccess = access; }
time_t GetCreateWhen(void) const { return fCreateWhen; }
void SetCreateWhen(time_t when) { fCreateWhen = when; }
time_t GetModWhen(void) const { return fModWhen; }
void SetModWhen(time_t when) { fModWhen = when; }
RecordKind GetRecordKind(void) const { return fRecordKind; }
void SetRecordKind(RecordKind recordKind) { fRecordKind = recordKind; }
const WCHAR* GetFormatStr(void) const { return fFormatStr; }
void SetFormatStr(const WCHAR* str) { fFormatStr = str; } // arg not copied, must be static!
LONGLONG GetCompressedLen(void) const { return fCompressedLen; }
void SetCompressedLen(LONGLONG len) { fCompressedLen = len; }
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; }
void SetRsrcForkLen(LONGLONG len) { fRsrcForkLen = len; }
DiskImg::FSFormat GetSourceFS(void) const { return fSourceFS; }
void SetSourceFS(DiskImg::FSFormat fmt) { fSourceFS = fmt; }
bool GetHasDataFork(void) const { return fHasDataFork; }
void SetHasDataFork(bool val) { fHasDataFork = val; }
bool GetHasRsrcFork(void) const { return fHasRsrcFork; }
void SetHasRsrcFork(bool val) { fHasRsrcFork = val; }
bool GetHasDiskImage(void) const { return fHasDiskImage; }
void SetHasDiskImage(bool val) { fHasDiskImage = val; }
bool GetHasComment(void) const { return fHasComment; }
void SetHasComment(bool val) { fHasComment = val; }
bool GetHasNonEmptyComment(void) const { return fHasNonEmptyComment; }
void SetHasNonEmptyComment(bool val) { fHasNonEmptyComment = val; }
bool GetDamaged(void) const { return fDamaged; }
void SetDamaged(bool val) { fDamaged = val; }
bool GetSuspicious(void) const { return fSuspicious; }
void SetSuspicious(bool val) { fSuspicious = val; }
GenericEntry* GetPrev(void) const { return fpPrev; }
void SetPrev(GenericEntry* pEntry) { fpPrev = pEntry; }
GenericEntry* GetNext(void) const { return fpNext; }
void SetNext(GenericEntry* pEntry) { fpNext = pEntry; }
// Utility functions.
const WCHAR* GetFileTypeString(void) const;
static bool CheckHighASCII(const BYTE* buffer,
unsigned long count);
static ConvertEOL DetermineConversion(const BYTE* buffer,
long count, EOLType* pSourceType, ConvertHighASCII* pConvHA);
static int GenericEntry::WriteConvert(FILE* fp, const char* buf,
size_t len, ConvertEOL* pConv, ConvertHighASCII* pConvHA,
bool* pLastCR);
protected:
static void SpacesToUnderscores(WCHAR* buf);
private:
WCHAR* fPathName;
const WCHAR* fFileName; // points within fPathName
const WCHAR* fFileNameExtension; // points within fPathName
char fFssep;
WCHAR* fSubVolName; // sub-volume prefix, or nil if none
WCHAR* fDisplayName; // combination of sub-vol and path
long fFileType;
long fAuxType;
long fAccess;
time_t fCreateWhen;
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
DiskImg::FSFormat fSourceFS; // if DOS3.3, text files have funky format
bool fHasDataFork;
bool fHasRsrcFork;
bool fHasDiskImage;
bool fHasComment;
bool fHasNonEmptyComment; // set if fHasComment and it isn't empty
bool fDamaged; // if set, don't try to open file
bool fSuspicious; // if set, file *might* be damaged
long fIndex; // serial index, for sorting "unsorted" view
GenericEntry* fpPrev;
GenericEntry* fpNext;
};
/*
* Generic representation of a collection of Apple II files.
*
* This raises the "reload" flag whenever its data is reloaded. Any code that
* keeps pointers to stuff (e.g. local copies of pointers to GenericEntry
* objects) needs to check the "reload" flag before dereferencing them.
*/
class GenericArchive {
public:
GenericArchive(void) {
fPathName = nil;
fNumEntries = 0;
fEntryHead = fEntryTail = nil;
fReloadFlag = true;
//fEntryIndex = nil;
}
virtual ~GenericArchive(void) {
//WMSG0("Deleting GenericArchive\n");
DeleteEntries();
delete fPathName;
}
virtual GenericEntry* GetEntries(void) const {
return fEntryHead;
}
virtual long GetNumEntries(void) const {
return fNumEntries;
}
//virtual GenericEntry* GetEntry(long num) {
// ASSERT(num >= 0 && num < fNumEntries);
// if (fEntryIndex == nil)
// CreateIndex();
// return fEntryIndex[num];
//}
typedef enum {
kResultUnknown = 0,
kResultSuccess, // open succeeded
kResultFailure, // open failed
kResultCancel, // open was cancelled by user
kResultFileArchive, // found a file archive rather than disk image
} OpenResult;
// Open an archive and do fun things with the innards.
virtual OpenResult Open(const WCHAR* filename, bool readOnly,
CString* pErrMsg) = 0;
// Create a new archive with the specified name.
virtual CString New(const WCHAR* filename, const void* options) = 0;
// Flush any unwritten data to disk
virtual CString Flush(void) = 0;
// Force a re-read from the underlying storage.
virtual CString Reload(void) = 0;
// Do we allow modification?
virtual bool IsReadOnly(void) const = 0;
// Does the underlying storage have un-flushed modifications?
virtual bool IsModified(void) const = 0;
virtual bool GetReloadFlag(void) { return fReloadFlag; }
virtual void ClearReloadFlag(void) { fReloadFlag = false; }
// One of these for every sub-class. This is used to ensure that, should
// we need to down-cast an object, we did it correctly (without needing
// to include RTTI support).
typedef enum {
kArchiveUnknown = 0,
kArchiveNuFX,
kArchiveBNY,
kArchiveACU,
kArchiveDiskImage,
} ArchiveKind;
virtual ArchiveKind GetArchiveKind(void) = 0;
// Get a nice description for the title bar.
virtual void GetDescription(CString* pStr) const = 0;
// Do a bulk add.
virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) = 0;
// Do a disk add.
virtual bool AddDisk(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) = 0;
// Create a subdirectory.
virtual bool CreateSubdir(CWnd* pMsgWnd, GenericEntry* pParentEntry,
const WCHAR* newName) = 0;
// Test a set of files.
virtual bool TestSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) = 0;
// Delete a set of files.
virtual bool DeleteSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) = 0;
// Rename a set of files.
virtual bool RenameSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) = 0;
virtual CString TestPathName(const GenericEntry* pGenericEntry,
const CString& basePath, const CString& newName, char newFssep) const = 0;
// Rename a volume (or sub-volume)
virtual bool RenameVolume(CWnd* pMsgWnd, DiskFS* pDiskFS,
const WCHAR* newName) = 0;
virtual CString TestVolumeName(const DiskFS* pDiskFS,
const WCHAR* newName) const = 0;
// Recompress a set of files.
virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
const RecompressOptionsDialog* pRecompOpts) = 0;
// Transfer files out of this archive and into another.
typedef enum {
kXferOK = 0, kXferFailed = 1, kXferCancelled = 2, kXferOutOfSpace = 3
} XferStatus;
virtual XferStatus XferSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
ActionProgressDialog* pActionProgress,
const XferFileOptions* pXferOpts) = 0;
// Get, set, or delete the comment on an entry.
virtual bool GetComment(CWnd* pMsgWnd, const GenericEntry* pEntry,
CString* pStr) = 0;
virtual bool SetComment(CWnd* pMsgWnd, GenericEntry* pEntry,
const CString& str) = 0;
virtual bool DeleteComment(CWnd* pMsgWnd, GenericEntry* pEntry) = 0;
// Set ProDOS file properties (e.g. file type, access flags).
virtual bool SetProps(CWnd* pMsgWnd, GenericEntry* pEntry,
const FileProps* pProps) = 0;
// Preferences have changed, update library state as needed.
virtual void PreferencesChanged(void) = 0;
// Determine an archive's capabilities. This is specific to the object
// instance, so this must not be made a static function.
typedef enum {
kCapUnknown = 0,
kCapCanTest, // NuFX, BNY
kCapCanRenameFullPath, // NuFX, BNY
kCapCanRecompress, // NuFX, BNY
kCapCanEditComment, // NuFX
kCapCanAddDisk, // NuFX
kCapCanConvEOLOnAdd, // Disk
kCapCanCreateSubdir, // Disk
kCapCanRenameVolume, // Disk
} Capability;
virtual long GetCapability(Capability cap) = 0;
// Get the pathname of the file we opened.
const WCHAR* GetPathName(void) const { return fPathName; }
// Generic utility function.
static CString GenDerivedTempName(const WCHAR* filename);
static int ComparePaths(const CString& name1, char fssep1,
const CString& name2, char fssep2);
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: xyzzy: NuFileDetails cast requires us to store pathnames with
* narrow strings.
*/
class FileDetails {
public:
FileDetails(void);
virtual ~FileDetails(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.)
*/
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);
}
/*
* What kind of file this is. Files being added to NuFX from Windows
* can't be "BothForks" because each 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.)
*/
typedef 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.
*/
//NuThreadID threadID; /* data, rsrc, disk img? */
FileKind entryKind;
/*
* 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. See also
* PathProposal::LocalToArchive().
*/
CString storageName;
//NuFileSysID fileSysID;
DiskImg::FSFormat fileSysFmt;
unsigned short fileSysInfo; /* fssep lurks here */
unsigned long access;
unsigned long fileType;
unsigned long extraType;
unsigned short storageType; /* "Unknown" or disk block size */
NuDateTime createWhen;
NuDateTime modWhen;
NuDateTime archiveWhen;
private:
static void CopyFields(FileDetails* pDst, const FileDetails* pSrc);
};
// Transfer files, one at a time, into this archive from another.
virtual void XferPrepare(const XferFileOptions* pXferOpts) = 0;
virtual CString XferFile(FileDetails* pDetails, BYTE** pDataBuf,
long dataLen, BYTE** pRsrcBuf, long rsrcLen) = 0;
virtual void XferAbort(CWnd* pMsgWnd) = 0;
virtual void XferFinish(CWnd* pMsgWnd) = 0;
static void UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime);
protected:
virtual void DeleteEntries(void);
/* NuLib2-derived recursive directory add functions */
void ReplaceFssep(WCHAR* str, char oldc, char newc, char newSubst);
NuError GetFileDetails(const AddFilesDialog* pAddOpts, const WCHAR* pathname,
struct _stat* psb, FileDetails* pDetails);
Win32dirent* OpenDir(const WCHAR* name);
Win32dirent* ReadDir(Win32dirent* dir);
void CloseDir(Win32dirent* dir);
NuError Win32AddDirectory(const AddFilesDialog* pAddOpts,
const WCHAR* dirName, CString* pErrMsg);
NuError Win32AddFile(const AddFilesDialog* pAddOpts,
const WCHAR* pathname, CString* pErrMsg);
NuError AddFile(const AddFilesDialog* pAddOpts, const WCHAR* pathname,
CString* pErrMsg);
/*
* Each implementation must provide this. It's called from the generic
* AddFile function with the high-level add options, a partial pathname,
* and a FileDetails structure filled in using Win32 calls.
*
* One call to AddFile can result in multiple calls to DoAddFile if
* the subject of the AddFile call is a directory (and fIncludeSubdirs
* is set).
*
* DoAddFile is not called for subdirectories. The underlying code must
* create directories as needed.
*
* In some cases (such as renaming a file as it is being added) the
* information in "*pDetails" may be modified.
*/
virtual NuError DoAddFile(const AddFilesDialog* pAddOpts,
FileDetails* pDetails) = 0;
void SetPathName(const WCHAR* pathName) {
free(fPathName);
if (pathName != nil) {
fPathName = _wcsdup(pathName);
} else {
fPathName = nil;
}
}
bool fReloadFlag; // set after Reload called
private:
//virtual void CreateIndex(void);
//CString fNewPathHolder;
//CString fOrigPathHolder;
WCHAR* fPathName;
long fNumEntries;
GenericEntry* fEntryHead;
GenericEntry* fEntryTail;
//GenericEntry** fEntryIndex;
};
/*
* One entry in a SelectionSet.
*/
class SelectionEntry {
public:
SelectionEntry(GenericEntry* pEntry) {
fpEntry = pEntry;
//fThreadKind = threadKind;
//fFilter = filter;
//fReformatName = "";
fpPrev = fpNext = nil;
}
~SelectionEntry(void) {}
int Reformat(ReformatHolder* pHolder);
GenericEntry* GetEntry(void) const { return fpEntry; }
//int GetThreadKind(void) const { return fThreadKind; }
//int GetFilter(void) const { return fFilter; }
//const char* GetReformatName(void) const { return fReformatName; }
SelectionEntry* GetPrev(void) const { return fpPrev; }
void SetPrev(SelectionEntry* pPrev) { fpPrev = pPrev; }
SelectionEntry* GetNext(void) const { return fpNext; }
void SetNext(SelectionEntry* pNext) { fpNext = pNext; }
private:
GenericEntry* fpEntry;
//int fThreadKind; // data, rsrc, etc (threadMask)
//int fFilter; // fAllowedFilters, really
//const char* fReformatName; // name of formatting actually applied
SelectionEntry* fpPrev;
SelectionEntry* fpNext;
};
class ContentList;
/*
* A set of selected files.
*
* Each entry represents one item that can be displayed, such as a data
* fork, resource fork, or comment thread. Thus, a single file may have
* multiple entries in the set.
*/
class SelectionSet {
public:
SelectionSet(void) {
fNumEntries = 0;
fEntryHead = fEntryTail = fIterCurrent = nil;
}
~SelectionSet(void) {
DeleteEntries();
}
// create the set from the selected members of a ContentList
void CreateFromSelection(ContentList* pContentList, int threadMask);
// create the set from all members of a ContentList
void CreateFromAll(ContentList* pContentList, int threadMask);
// get the head of the list
SelectionEntry* GetEntries(void) const { return fEntryHead; }
void IterReset(void) {
fIterCurrent = nil;
}
// move to the next or previous entry as part of iterating
SelectionEntry* IterPrev(void) {
if (fIterCurrent == nil)
fIterCurrent = fEntryTail;
else
fIterCurrent = fIterCurrent->GetPrev();
return fIterCurrent;
}
SelectionEntry* IterNext(void) {
if (fIterCurrent == nil)
fIterCurrent = fEntryHead;
else
fIterCurrent = fIterCurrent->GetNext();
return fIterCurrent;
}
SelectionEntry* IterCurrent(void) {
return fIterCurrent;
}
bool IterHasPrev(void) const {
if (fIterCurrent == nil)
return fEntryTail != nil;
else
return (fIterCurrent->GetPrev() != nil);
}
bool IterHasNext(void) const {
if (fIterCurrent == nil)
return fEntryHead != nil;
else
return (fIterCurrent->GetNext() != nil);
}
int GetNumEntries(void) const { return fNumEntries; }
// count the #of entries whose display name matches "prefix"
int CountMatchingPrefix(const WCHAR* prefix);
// debug dump
void Dump(void);
private:
void AddToSet(GenericEntry* pEntry, int threadMask);
void AddEntry(SelectionEntry* pEntry);
void DeleteEntries(void);
int fNumEntries;
SelectionEntry* fIterCurrent;
SelectionEntry* fEntryHead;
SelectionEntry* fEntryTail;
};
#endif /*APP_GENERICARCHIVE_H*/