/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * DiskFS base class. */ #include "StdAfx.h" #include "DiskImgPriv.h" /* * =========================================================================== * A2File * =========================================================================== */ /* * Set the quality level (a/k/a damage level) of a file. * * Refuse to "improve" the quality level of a file. */ void A2File::SetQuality(FileQuality quality) { if (quality == kQualityGood && (fFileQuality == kQualitySuspicious || fFileQuality == kQualityDamaged)) { assert(false); return; } if (quality == kQualitySuspicious && fFileQuality == kQualityDamaged) { //assert(false); return; } fFileQuality = quality; } /* * Reset the quality level after making repairs. */ void A2File::ResetQuality(void) { fFileQuality = kQualityGood; } /* * =========================================================================== * DiskFS * =========================================================================== */ /* * Set the DiskImg pointer. We add or subtract from the DiskImg's ref count * so that it can be sure there are no DiskFS objects left dangling when the * DiskImg is deleted. */ void DiskFS::SetDiskImg(DiskImg* pImg) { if (pImg == nil && fpImg == nil) { WMSG0("SetDiskImg: no-op (both nil)\n"); return; } else if (fpImg == pImg) { WMSG0("SetDiskImg: no-op (old == new)\n"); return; } if (fpImg != nil) fpImg->RemoveDiskFS(this); if (pImg != nil) pImg->AddDiskFS(this); fpImg = pImg; } /* * Flush changes to disk. */ DIError DiskFS::Flush(DiskImg::FlushMode mode) { SubVolume* pSubVol = GetNextSubVolume(nil); DIError dierr; while (pSubVol != nil) { // quick sanity check assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg()); dierr = pSubVol->GetDiskFS()->Flush(mode); // recurse if (dierr != kDIErrNone) return dierr; pSubVol = GetNextSubVolume(pSubVol); } assert(fpImg != nil); return fpImg->FlushImage(mode); } /* * Set the "read only" flag on our DiskImg and those of our sub-volumes. */ void DiskFS::SetAllReadOnly(bool val) { SubVolume* pSubVol = GetNextSubVolume(nil); /* put current volume in read-only mode */ if (fpImg != nil) fpImg->SetReadOnly(val); /* handle our kids */ while (pSubVol != nil) { // quick sanity check assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg()); //pSubVol->GetDiskImg()->SetReadOnly(val); pSubVol->GetDiskFS()->SetAllReadOnly(val); // recurse pSubVol = GetNextSubVolume(pSubVol); } } /* * The file list looks something like this: * * volume-dir * file1 * file2 * subdir1 * subdir1:file1 * subdir1:file2 * subdir1:subsub1 * subdir1:subsub1:file1 * subdir1:subsub2 * subdir1:subsub2:file1 * subdir1:subsub2:file2 * subdir1:file3 * file3 * * Everything contained within a subdir comes after the subdir entry and * before any entries from later subdirs at the same level. * * It's unclear whether a linear list or a hierarchical tree structure is * the most appropriate way to hold the data. The tree is easier to update, * but the linear list corresponds to the primary view in CiderPress, and * lists are simpler and easier to manage. For now I'm sticking with a list. * * The files MUST be in the order in which they came from the disk. This * doesn't matter most of the time, but for Pascal volumes it's essential * for ensuring that the Write command doesn't run over the next file. */ /* * Add a file to the end of our list. */ void DiskFS::AddFileToList(A2File* pFile) { assert(pFile->GetNext() == nil); if (fpA2Head == nil) { assert(fpA2Tail == nil); fpA2Head = fpA2Tail = pFile; } else { pFile->SetPrev(fpA2Tail); fpA2Tail->SetNext(pFile); fpA2Tail = pFile; } } /* * Insert a file into its appropriate place in the list, based on a file * hierarchy. * * Pass in the thing to be added ("pFile") and the previous entry ("pPrev"). * An empty hierarchic filesystem will have an entry for the volume dir, so * we should never have an empty list or a NULL pPrev. * * The part where things go pear-shaped happens if "pPrev" is a subdirectory. * If so, we need to come after all of the subdir's entries, including any * entries for sub-subdirs. There's no graceful way to go about this in a * linear list. * * (We'd love to be able to find the *next* entry and then back up one, * but odds are that there isn't a "next" entry if we're busily creating * files.) */ void DiskFS::InsertFileInList(A2File* pFile, A2File* pPrev) { assert(pFile->GetNext() == nil); if (fpA2Head == nil) { assert(pPrev == nil); fpA2Head = fpA2Tail = pFile; return; } else if (pPrev == nil) { // create two entries on DOS disk, delete first, add new file pFile->SetNext(fpA2Head); fpA2Head = pFile; return; } /* * If we're inserting after the parent (i.e. we're the very first thing * in a subdir) or after a plain file, just drop it in. * * If we're inserting after a subdir, go fish. */ if (pPrev->IsDirectory() && pFile->GetParent() != pPrev) { pPrev = SkipSubdir(pPrev); } pFile->SetNext(pPrev->GetNext()); pPrev->SetNext(pFile); } /* * Skip over all entries in the subdir we're pointing to. * * The return value is the very last entry in the subdir. */ A2File* DiskFS::SkipSubdir(A2File* pSubdir) { if (pSubdir->GetNext() == nil) return pSubdir; // end of list reached -- subdir is empty A2File* pCur = pSubdir; A2File* pNext = nil; assert(pCur != nil); // at least one time through the loop while (pCur != nil) { pNext = pCur->GetNext(); if (pNext == nil) // end of list reached return pCur; if (pNext->GetParent() != pSubdir) // end of dir reached return pCur; if (pNext->IsDirectory()) pCur = SkipSubdir(pNext); // get last entry in dir else pCur = pNext; // advance forward one } /* should never get here */ assert(false); return pNext; } /* * Delete a member from the list. * * We're currently singly-linked, making this rather expensive. */ void DiskFS::DeleteFileFromList(A2File* pFile) { if (fpA2Head == pFile) { /* delete the head of the list */ fpA2Head = fpA2Head->GetNext(); delete pFile; } else { A2File* pCur = fpA2Head; while (pCur != nil) { if (pCur->GetNext() == pFile) { /* found it */ A2File* pNextNext = pCur->GetNext()->GetNext(); delete pCur->GetNext(); pCur->SetNext(pNextNext); break; } pCur = pCur->GetNext(); } if (pCur == nil) { WMSG0("GLITCH: couldn't find element to delete!\n"); assert(false); } } } /* * Access the "next" pointer. * * Because we apparently can't declare an anonymous class as a friend * in MSVC++6.0, this can't be an inline function. */ A2File* DiskFS::GetNextFile(A2File* pFile) const { if (pFile == NULL) return fpA2Head; else return pFile->GetNext(); } /* * Return the #of elements in the linear file list. * * Right now the only code that calls this is the disk info panel in * CiderPress, so we don't need it to be efficient. */ long DiskFS::GetFileCount(void) const { long count = 0; A2File* pFile = fpA2Head; while (pFile != nil) { count++; pFile = pFile->GetNext(); } return count; } /* * Delete all entries in the list. */ void DiskFS::DeleteFileList(void) { A2File* pFile; A2File* pNext; pFile = fpA2Head; while (pFile != nil) { pNext = pFile->GetNext(); delete pFile; pFile = pNext; } } /* * Dump file list. */ void DiskFS::DumpFileList(void) { A2File* pFile; WMSG0("DiskFS file list contents:\n"); pFile = GetNextFile(nil); while (pFile != nil) { WMSG1(" %s\n", pFile->GetPathName()); pFile = GetNextFile(pFile); } } /* * Run through the list of files and find one that matches (case-insensitive). * * This does not attempt to open files in sub-volumes. We could, but it's * likely that the application has "decorated" the name in some fashion, * e.g. by prepending the sub-volume's volume name to the filename. May * be best to let the application dig for the sub-volume. */ A2File* DiskFS::GetFileByName(const char* fileName, StringCompareFunc func) { A2File* pFile; if (func == nil) func = ::strcasecmp; pFile = GetNextFile(nil); while (pFile != nil) { if ((*func)(pFile->GetPathName(), fileName) == 0) return pFile; pFile = GetNextFile(pFile); } return nil; } /* * Add a sub-volume to the end of our list. * * Copies some parameters from "this" into pDiskFS, such as whether to * scan for sub-volumes and the various DiskFS parameters. * * Note this happens AFTER the disk has been scanned. */ void DiskFS::AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS) { SubVolume* pSubVol; /* * Check the arguments. */ if (pDiskImg == nil || pDiskFS == nil) { WMSG2(" DiskFS bogus sub volume ptrs %08lx %08lx\n", (long) pDiskImg, (long) pDiskFS); assert(false); return; } if (pDiskImg == fpImg || pDiskFS == this) { WMSG0(" DiskFS attempt to add self to sub-vol list\n"); assert(false); return; } if (pDiskFS->GetDiskImg() == nil) { WMSG0(" DiskFS lacks a DiskImg pointer\n"); assert(false); return; } pSubVol = fpSubVolumeHead; while (pSubVol != nil) { if (pSubVol->GetDiskImg() == pDiskImg || pSubVol->GetDiskFS() == pDiskFS) { WMSG0(" DiskFS multiple adds on diskimg or diskfs\n"); assert(false); return; } pSubVol = pSubVol->GetNext(); } assert(pDiskFS->GetDiskImg() == pDiskImg); /* * Looks good. Add it. */ pSubVol = new SubVolume; if (pSubVol == nil) return; pSubVol->Create(pDiskImg, pDiskFS); if (fpSubVolumeHead == nil) { assert(fpSubVolumeTail == nil); fpSubVolumeHead = fpSubVolumeTail = pSubVol; } else { pSubVol->SetPrev(fpSubVolumeTail); fpSubVolumeTail->SetNext(pSubVol); fpSubVolumeTail = pSubVol; } /* make sure inheritable stuff gets copied */ CopyInheritables(pDiskFS); } /* * Copy parameters to a sub-volume. */ void DiskFS::CopyInheritables(DiskFS* pNewFS) { for (int i = 0; i < (int) NELEM(fParmTable); i++) pNewFS->fParmTable[i] = fParmTable[i]; pNewFS->fScanForSubVolumes = fScanForSubVolumes; #if 0 /* copy scan progress update stuff */ pNewFS->fpScanProgressCallback = fpScanProgressCallback; pNewFS->fpScanProgressCookie = fpScanProgressCookie; pNewFS->fpScanCount = -1; strcpy(pNewFS->fpScanMsg, "HEY"); #endif } /* * Access the "next" pointer. * * Because we apparently can't declare an anonymous class as a friend * in MSVC++6.0, this can't be an inline function. */ DiskFS::SubVolume* DiskFS::GetNextSubVolume(const SubVolume* pSubVol) const { if (pSubVol == NULL) return fpSubVolumeHead; else return pSubVol->GetNext(); } /* * Delete all entries in the list. */ void DiskFS::DeleteSubVolumeList(void) { SubVolume* pSubVol; SubVolume* pNext; pSubVol = fpSubVolumeHead; while (pSubVol != nil) { pNext = pSubVol->GetNext(); delete pSubVol; pSubVol = pNext; } } /* * Get a parameter. */ long DiskFS::GetParameter(DiskFSParameter parm) { assert(parm > kParmUnknown && parm < kParmMax); return fParmTable[parm]; } /* * Set a parameter. * * The setting propagates to all sub-volumes. */ void DiskFS::SetParameter(DiskFSParameter parm, long val) { assert(parm > kParmUnknown && parm < kParmMax); fParmTable[parm] = val; SubVolume* pSubVol = GetNextSubVolume(nil); while (pSubVol != nil) { pSubVol->GetDiskFS()->SetParameter(parm, val); pSubVol = GetNextSubVolume(pSubVol); } } /* * Scan for damaged or suspicious files. */ void DiskFS::ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious) { A2File* pFile; *pDamaged = *pSuspicious = false; pFile = GetNextFile(nil); while (pFile != nil) { if (pFile->GetQuality() == A2File::kQualityDamaged) *pDamaged = true; if (pFile->GetQuality() != A2File::kQualityGood) *pSuspicious = true; pFile = GetNextFile(pFile); } }