/* * NuLib2 * Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved. * This is free software; you can redistribute it and/or modify it under the * terms of the BSD License, see the file COPYING. * * List files in the archive. */ #include "NuLib2.h" /* kinds of records */ enum RecordKind { kRecordKindUnknown = 0, kRecordKindDisk, kRecordKindFile, kRecordKindForkedFile }; static const char* gShortFormatNames[] = { "unc", "squ", "lz1", "lz2", "u12", "u16", "dfl", "bzp" }; #if 0 /* days of the week */ static const char* gDayNames[] = { "[ null ]", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; #endif /* months of the year */ static const char* gMonths[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /* * Compute a percentage. */ int ComputePercent(uint32_t totalSize, uint32_t size) { int perc; if (!totalSize && !size) return 100; /* file is zero bytes long */ if (totalSize < 21474836) perc = (totalSize * 100) / size; else perc = totalSize / (size/100); /* don't say "0%" if it's not actually zero... it looks dumb */ if (!perc && size) perc = 1; return perc; } /* * Convert a NuDateTime structure into something printable. This uses an * abbreviated format, with date and time but not weekday or seconds. * * The buffer passed in must hold at least kDateOutputLen bytes. * * Returns "buffer" for the benefit of printf() calls. */ char* FormatDateShort(const NuDateTime* pDateTime, char* buffer) { /* is it valid? */ if (pDateTime->day > 30 || pDateTime->month > 11 || pDateTime->hour > 24 || pDateTime->minute > 59) { strcpy(buffer, " "); goto bail; } /* is it empty? */ if ((pDateTime->second | pDateTime->minute | pDateTime->hour | pDateTime->year | pDateTime->day | pDateTime->month | pDateTime->extra | pDateTime->weekDay) == 0) { strcpy(buffer, " [No Date] "); goto bail; } sprintf(buffer, "%02d-%s-%02d %02d:%02d", pDateTime->day+1, gMonths[pDateTime->month], pDateTime->year % 100, pDateTime->hour, pDateTime->minute); bail: return buffer; } /* * NuStream callback function. Displays the filename. */ static NuResult ShowContentsShort(NuArchive* pArchive, void* vpRecord) { const NuRecord* pRecord = (NuRecord*) vpRecord; NulibState* pState; Assert(pArchive != NULL); (void) NuGetExtraData(pArchive, (void**) &pState); Assert(pState != NULL); if (!IsSpecified(pState, pRecord)) goto bail; UNICHAR* filenameUNI = CopyMORToUNI(pRecord->filenameMOR); printf("%s\n", filenameUNI == NULL ? "" : filenameUNI); free(filenameUNI); bail: return kNuOK; } /* * Analyze the contents of a record to determine if it's a disk, file, * or "other". Compute the total compressed and uncompressed lengths * of all data threads. Return the "best" format. * * The "best format" and "record type" stuff assume that the entire * record contains only a disk thread or a file thread, and that any * format is interesting so long as it isn't "no compression". In * general these will be true, because ShrinkIt and NuLib create files * this way. * * You could, of course, create a single record with a data thread and * a disk image thread, but it's a fair bet ShrinkIt would ignore one * or the other. * * NOTE: GSHK likes to omit the data threads for zero-length data and * resource forks. That screws up analysis by scanning for threads. We * can work around missing resource forks by simply checking the record's * storage type. We could be clever and detect records that have no * data-class threads at all, and no additional threads other than a * comment and filename, but this is just for display. ShrinkIt doesn't * handle these records correctly in all cases, so flagging them in a * user-visible way seems reasonable. */ static NuError AnalyzeRecord(const NuRecord* pRecord, enum RecordKind* pRecordKind, uint16_t* pFormat, uint32_t* pTotalLen, uint32_t* pTotalCompLen) { const NuThread* pThread; NuThreadID threadID; uint32_t idx; *pRecordKind = kRecordKindUnknown; *pTotalLen = *pTotalCompLen = 0; *pFormat = kNuThreadFormatUncompressed; for (idx = 0; idx < pRecord->recTotalThreads; idx++) { pThread = NuGetThread(pRecord, idx); Assert(pThread != NULL); if (pThread->thThreadClass == kNuThreadClassData) { /* replace what's there if this might be more interesting */ if (*pFormat == kNuThreadFormatUncompressed) *pFormat = pThread->thThreadFormat; threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind); if (threadID == kNuThreadIDRsrcFork) *pRecordKind = kRecordKindForkedFile; else if (threadID == kNuThreadIDDiskImage) *pRecordKind = kRecordKindDisk; else if (threadID == kNuThreadIDDataFork && *pRecordKind == kRecordKindUnknown) *pRecordKind = kRecordKindFile; /* sum up, so we get both forks of forked files */ *pTotalLen += pThread->actualThreadEOF; *pTotalCompLen += pThread->thCompThreadEOF; } } /* * Fix up the case where we have a forked file, but GSHK decided not * to include a resource fork in the record. */ if (pRecord->recStorageType == kNuStorageExtended && *pRecordKind != kRecordKindForkedFile) { *pRecordKind = kRecordKindForkedFile; } return kNuErrNone; } /* * NuStream callback function. Displays the filename and several attributes. * * This is intended to mimic the output of some old version of ProDOS 8 * ShrinkIt. */ static NuResult ShowContentsVerbose(NuArchive* pArchive, void* vpRecord) { NuError err = kNuErrNone; const NuRecord* pRecord = (NuRecord*) vpRecord; enum RecordKind recordKind; uint32_t totalLen, totalCompLen; uint16_t format; NulibState* pState; char date1[kDateOutputLen]; char tmpbuf[16]; int len; Assert(pArchive != NULL); (void) NuGetExtraData(pArchive, (void**) &pState); Assert(pState != NULL); if (!IsSpecified(pState, pRecord)) goto bail; err = AnalyzeRecord(pRecord, &recordKind, &format, &totalLen, &totalCompLen); if (err != kNuErrNone) goto bail; /* * Display the filename, truncating if it's longer than 27 characters. * * Attempting to do column layout with printf string formatting (e.g. * "%-27s") doesn't really work for UTF-8 because printf() is * operating on bytes, and the conversion to a Unicode code point * is happening in the terminal. We need to do the spacing ourselves, * using the fact that one MOR character turns into one Unicode character. * * If the display isn't converting multi-byte sequences to individual * characters, this won't look right, but we can't make everybody happy. */ static const char kSpaces[27+1] = " "; UNICHAR* filenameUNI; len = strlen(pRecord->filenameMOR); if (len <= 27) { filenameUNI = CopyMORToUNI(pRecord->filenameMOR); printf("%c%s%s ", IsRecordReadOnly(pRecord) ? '+' : ' ', filenameUNI, &kSpaces[len]); } else { filenameUNI = CopyMORToUNI(pRecord->filenameMOR + len - 25); printf("%c..%s ", IsRecordReadOnly(pRecord) ? '+' : ' ', filenameUNI); } free(filenameUNI); switch (recordKind) { case kRecordKindUnknown: printf("%s- $%04X ", GetFileTypeString(pRecord->recFileType), pRecord->recExtraType); break; case kRecordKindDisk: sprintf(tmpbuf, "%dk", totalLen / 1024); printf("Disk %-6s ", tmpbuf); break; case kRecordKindFile: case kRecordKindForkedFile: printf("%s%c $%04X ", GetFileTypeString(pRecord->recFileType), recordKind == kRecordKindForkedFile ? '+' : ' ', pRecord->recExtraType); break; default: Assert(0); printf("ERROR "); } printf("%s ", FormatDateShort(&pRecord->recArchiveWhen, date1)); if (format >= NELEM(gShortFormatNames)) printf("??? "); else printf("%s ", gShortFormatNames[format]); /* compute the percent size */ if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen)) printf("--- "); /* weird */ else if (totalLen < totalCompLen) printf(">100%% "); /* compression failed? */ else { sprintf(tmpbuf, "%02d%%", ComputePercent(totalCompLen, totalLen)); printf("%4s ", tmpbuf); } if (!totalLen && totalCompLen) printf(" ????"); /* weird */ else printf("%8u", totalLen); printf("\n"); NState_AddToTotals(pState, totalLen, totalCompLen); bail: if (err != kNuErrNone) { filenameUNI = CopyMORToUNI(pRecord->filenameMOR); printf("(ERROR on '%s')\n", filenameUNI == NULL ? "" : filenameUNI); free(filenameUNI); } return kNuOK; } /* * Print a short listing of the contents of an archive. */ NuError DoListShort(NulibState* pState) { NuError err; NuArchive* pArchive = NULL; Assert(pState != NULL); if (NState_GetModBinaryII(pState)) return BNYDoListShort(pState); err = OpenArchiveReadOnly(pState); if (err == kNuErrIsBinary2) return BNYDoListShort(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); Assert(pArchive != NULL); err = NuContents(pArchive, ShowContentsShort); /* fall through with err */ bail: if (pArchive != NULL) (void) NuClose(pArchive); return err; } /* * Print a more verbose listing of the contents of an archive. */ NuError DoListVerbose(NulibState* pState) { NuError err; NuArchive* pArchive = NULL; const NuMasterHeader* pHeader; char date1[kDateOutputLen]; char date2[kDateOutputLen]; long totalLen, totalCompLen; const char* cp; Assert(pState != NULL); if (NState_GetModBinaryII(pState)) return BNYDoListVerbose(pState); err = OpenArchiveReadOnly(pState); if (err == kNuErrIsBinary2) return BNYDoListVerbose(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); Assert(pArchive != NULL); /* * Try to get just the filename. */ if (IsFilenameStdin(NState_GetArchiveFilename(pState))) cp = ""; else cp = FilenameOnly(pState, NState_GetArchiveFilename(pState)); /* grab the master header block */ err = NuGetMasterHeader(pArchive, &pHeader); if (err != kNuErrNone) goto bail; printf(" %-15.15s Created:%s Mod:%s Recs:%5u\n\n", cp, FormatDateShort(&pHeader->mhArchiveCreateWhen, date1), FormatDateShort(&pHeader->mhArchiveModWhen, date2), pHeader->mhTotalRecords); printf(" Name Type Auxtyp Archived" " Fmat Size Un-Length\n"); printf("-------------------------------------------------" "----------------------------\n"); err = NuContents(pArchive, ShowContentsVerbose); if (err != kNuErrNone) goto bail; /* * Show the totals. NuFX overhead can be as much as 25% for archives * with lots of small files. */ NState_GetTotals(pState, &totalLen, &totalCompLen); printf("-------------------------------------------------" "----------------------------\n"); printf(" Uncomp: %ld Comp: %ld %%of orig: %d%%\n", totalLen, totalCompLen, totalLen == 0 ? 0 : ComputePercent(totalCompLen, totalLen)); #ifdef DEBUG_VERBOSE if (!NState_GetFilespecCount(pState)) { printf(" Overhead: %ld (%d%%)\n", pHeader->mhMasterEOF - totalCompLen, ComputePercent(pHeader->mhMasterEOF - totalCompLen, pHeader->mhMasterEOF)); } #endif /*(void) NuDebugDumpArchive(pArchive);*/ bail: if (pArchive != NULL) (void) NuClose(pArchive); return err; } /* * Null callback, for those times when you don't really want to do anything. */ static NuResult NullCallback(NuArchive* pArchive, void* vpRecord) { return kNuOK; } /* * Print very detailed output, suitable for debugging (requires that * debug messages be enabled in nufxlib). */ NuError DoListDebug(NulibState* pState) { NuError err; NuArchive* pArchive = NULL; Assert(pState != NULL); if (NState_GetModBinaryII(pState)) return BNYDoListDebug(pState); err = OpenArchiveReadOnly(pState); if (err == kNuErrIsBinary2) return BNYDoListDebug(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); Assert(pArchive != NULL); /* have to do something to force the library to scan the archive */ err = NuContents(pArchive, NullCallback); if (err != kNuErrNone) goto bail; err = NuDebugDumpArchive(pArchive); if (err != kNuErrNone) fprintf(stderr, "ERROR: debugging not enabled in nufxlib\n"); /* fall through with err */ bail: if (pArchive != NULL) (void) NuClose(pArchive); return err; }