ciderpress/nufxlib/Funnel.c
Andy McFadden 84706d7ea4 Update NufxLib snapshot to v3.0.0d2
This integrates the latest NufxLib sources, and updates CiderPress
to work with the API changes.
2015-01-04 11:29:51 -08:00

905 lines
26 KiB
C

/*
* NuFX archive manipulation library
* 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-LIB.
*
* Implementation of NuFunnel, NuStraw and ProgressUpdater.
*/
#include "NufxLibPriv.h"
/*
* ===========================================================================
* Progress updater
* ===========================================================================
*/
/*
* Initialize the fields in a ProgressData structure, prior to compressing
* data into a record.
*
* The same structure will be used when expanding all threads in a given
* record.
*/
NuError Nu_ProgressDataInit_Compress(NuArchive* pArchive,
NuProgressData* pProgressData, const NuRecord* pRecord,
const UNICHAR* origPathnameUNI, const UNICHAR* pathnameUNI)
{
const char* cp;
Assert(pProgressData != NULL);
Assert(pArchive != NULL);
Assert(pRecord != NULL);
Assert(origPathnameUNI != NULL);
Assert(pathnameUNI != NULL);
pProgressData->pRecord = pRecord;
pProgressData->origPathnameUNI = origPathnameUNI;
pProgressData->pathnameUNI = pathnameUNI;
cp = strrchr(pathnameUNI, NuGetSepFromSysInfo(pRecord->recFileSysInfo));
if (cp == NULL || *(cp+1) == '\0')
pProgressData->filenameUNI = pProgressData->pathnameUNI;
else
pProgressData->filenameUNI = cp+1;
pProgressData->operation = kNuOpAdd;
pProgressData->state = kNuProgressPreparing;
pProgressData->uncompressedLength = 0;
pProgressData->uncompressedProgress = 0;
pProgressData->compress.threadFormat = (NuThreadFormat)-1;
/* ya know... if this is NULL, none of the above matters much */
pProgressData->progressFunc = pArchive->progressUpdaterFunc;
return kNuErrNone;
}
/*
* Initialize the fields in a ProgressData structure, prior to expanding
* data from a record.
*
* The same structure will be used when expanding all threads in a given
* record.
*/
NuError Nu_ProgressDataInit_Expand(NuArchive* pArchive,
NuProgressData* pProgressData, const NuRecord* pRecord,
const UNICHAR* newPathnameUNI, UNICHAR newFssep,
const UNICHAR* origPathnameUNI, NuValue convertEOL)
{
const NuThread* pThreadIter;
const char* cp;
int i;
Assert(pProgressData != NULL);
Assert(pArchive != NULL);
Assert(pRecord != NULL);
Assert(newPathnameUNI != NULL);
Assert(origPathnameUNI != NULL);
Assert(newFssep != 0);
pProgressData->pRecord = pRecord;
pProgressData->expand.pThread = NULL;
pProgressData->origPathnameUNI = origPathnameUNI;
pProgressData->pathnameUNI = newPathnameUNI;
cp = strrchr(newPathnameUNI, newFssep);
if (cp == NULL || *(cp+1) == '\0')
pProgressData->filenameUNI = newPathnameUNI;
else
pProgressData->filenameUNI = cp+1;
pProgressData->expand.convertEOL = convertEOL;
/* total up the data threads */
pProgressData->expand.totalCompressedLength = 0;
pProgressData->expand.totalUncompressedLength = 0;
for (i = 0; i < (int)pRecord->recTotalThreads; i++) {
pThreadIter = Nu_GetThread(pRecord, i);
if (pThreadIter->thThreadClass != kNuThreadClassData)
continue;
pProgressData->expand.totalCompressedLength += pThreadIter->thCompThreadEOF;
pProgressData->expand.totalUncompressedLength += pThreadIter->actualThreadEOF;
}
pProgressData->operation = kNuOpExtract;
if (pArchive->testMode)
pProgressData->operation = kNuOpTest;
pProgressData->state = kNuProgressPreparing;
pProgressData->uncompressedLength = 0;
pProgressData->uncompressedProgress = 0;
/* ya know... if this is NULL, none of the above matters much */
pProgressData->progressFunc = pArchive->progressUpdaterFunc;
return kNuErrNone;
}
/*
* Do the setup on a ProgressData prior to compressing a thread.
*/
NuError Nu_ProgressDataCompressPrep(NuArchive* pArchive, NuStraw* pStraw,
NuThreadFormat threadFormat, uint32_t sourceLen)
{
NuProgressData* pProgressData;
Assert(pArchive != NULL);
Assert(pStraw != NULL);
Assert(sourceLen < 32767*65536);
pProgressData = pStraw->pProgress;
if (pProgressData == NULL)
return kNuErrNone;
pProgressData->uncompressedLength = sourceLen;
pProgressData->compress.threadFormat = threadFormat;
return kNuErrNone;
}
/*
* Do the setup on a ProgressData prior to expanding a thread.
*
* "pThread" is the thread being expanded.
*/
NuError Nu_ProgressDataExpandPrep(NuArchive* pArchive, NuFunnel* pFunnel,
const NuThread* pThread)
{
NuProgressData* pProgressData;
Assert(pArchive != NULL);
Assert(pFunnel != NULL);
Assert(pThread != NULL);
pProgressData = pFunnel->pProgress;
if (pProgressData == NULL)
return kNuErrNone;
/*pProgressData->compressedLength = pThread->thCompThreadEOF;*/
pProgressData->uncompressedLength = pThread->actualThreadEOF;
pProgressData->expand.pThread = pThread;
return kNuErrNone;
}
/*
* Compute a completion percentage.
*/
static int Nu_ComputePercent(uint32_t total, uint32_t progress)
{
uint32_t perc;
if (!total)
return 0;
if (total < 21474836) {
perc = (progress * 100 + 50) / total;
if (perc > 100)
perc = 100;
} else {
perc = progress / (total / 100);
if (perc > 100)
perc = 100;
}
return (int) perc;
}
/*
* Send the initial progress message, before the output file is opened
* (when extracting) or the input file is opened (when adding).
*/
NuError Nu_SendInitialProgress(NuArchive* pArchive, NuProgressData* pProgress)
{
NuResult result;
Assert(pArchive != NULL);
Assert(pProgress != NULL);
if (pProgress->progressFunc == NULL)
return kNuErrNone;
pProgress->percentComplete = Nu_ComputePercent(
pProgress->uncompressedLength, pProgress->uncompressedProgress);
result = (*pProgress->progressFunc)(pArchive, (NuProgressData*) pProgress);
if (result == kNuSkip)
return kNuErrSkipped; /* [dunno how well this works] */
if (result == kNuAbort)
return kNuErrAborted;
return kNuErrNone;
}
/*
* ===========================================================================
* NuFunnel object
* ===========================================================================
*/
/*
* Allocate and initialize a Funnel.
*/
NuError Nu_FunnelNew(NuArchive* pArchive, NuDataSink* pDataSink,
NuValue convertEOL, NuValue convertEOLTo, NuProgressData* pProgress,
NuFunnel** ppFunnel)
{
NuError err = kNuErrNone;
NuFunnel* pFunnel = NULL;
Assert(ppFunnel != NULL);
Assert(pDataSink != NULL);
Assert(convertEOL == kNuConvertOff ||
convertEOL == kNuConvertOn ||
convertEOL == kNuConvertAuto);
pFunnel = Nu_Calloc(pArchive, sizeof(*pFunnel));
BailAlloc(pFunnel);
pFunnel->buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);
BailAlloc(pFunnel->buffer);
pFunnel->pDataSink = pDataSink;
pFunnel->convertEOL = convertEOL;
pFunnel->convertEOLTo = convertEOLTo;
pFunnel->convertEOLFrom = kNuEOLUnknown;
pFunnel->pProgress = pProgress;
pFunnel->checkStripHighASCII = (pArchive->valStripHighASCII != 0);
pFunnel->doStripHighASCII = false; /* determined on first write */
pFunnel->isFirstWrite = true;
bail:
if (err != kNuErrNone)
Nu_FunnelFree(pArchive, pFunnel);
else
*ppFunnel = pFunnel;
return err;
}
/*
* Free a Funnel.
*
* The data should already have been written; it's not the duty of a
* "free" function to flush data out.
*/
NuError Nu_FunnelFree(NuArchive* pArchive, NuFunnel* pFunnel)
{
if (pFunnel == NULL)
return kNuErrNone;
#ifdef DEBUG_MSGS
if (pFunnel->bufCount)
Nu_ReportError(NU_BLOB_DEBUG, kNuErrNone,
"freeing non-empty funnel");
#endif
Nu_Free(pArchive, pFunnel->buffer);
Nu_Free(pArchive, pFunnel);
return kNuErrNone;
}
#if 0
/*
* Set the maximum amount of output we're willing to push through the
* funnel. Attempts to write more than this many bytes will fail. This
* allows us to bail out as soon as it's apparent that compression is
* failing and is actually resulting in a larger file.
*/
void Nu_FunnelSetMaxOutput(NuFunnel* pFunnel, uint32_t maxBytes)
{
Assert(pFunnel != NULL);
Assert(maxBytes > 0);
pFunnel->outMax = maxBytes;
if (pFunnel->outCount >= pFunnel->outMax)
pFunnel->outMaxExceeded = true;
else
pFunnel->outMaxExceeded = false;
}
#endif
/*
* Check to see if this is a high-ASCII file. To qualify, EVERY
* character must have its high bit set, except for spaces (0x20).
* (The exception is courtesy Glen Bredon's "Merlin".)
*/
static Boolean Nu_CheckHighASCII(const NuFunnel* pFunnel, const uint8_t* buffer,
uint32_t count)
{
Boolean isHighASCII;
Assert(buffer != NULL);
Assert(count != 0);
Assert(pFunnel->checkStripHighASCII);
isHighASCII = true;
while (count--) {
if ((*buffer & 0x80) == 0 && *buffer != 0x20) {
isHighASCII = false;
break;
}
buffer++;
}
return isHighASCII;
}
/*
* Table determining what's a binary character and what isn't. It would
* possibly be more compact to generate this from a simple description,
* but I'm hoping static/const data will end up in the code segment and
* save space on the heap.
*
* This corresponds to less-316's ISO-latin1 "8bcccbcc18b95.33b.". This
* may be too loose by itself; we may want to require that the lower-ASCII
* values appear in higher proportions than the upper-ASCII values.
* Otherwise we run the risk of converting a binary file with specific
* properties. (Note that "upper-ASCII" refers to umlauts and other
* accented characters, not DOS 3.3 "high ASCII".)
*
* The auto-detect mechanism will never be perfect though, so there's not
* much point in tweaking it to death.
*/
static const char gNuIsBinary[256] = {
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, /* ^@-^O */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P-^_ */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* - / */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - ? */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ - O */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* P - _ */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* p - DEL */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 */
};
#define kNuMaxUpperASCII 1 /* max #of binary chars per 100 bytes */
#define kNuMinConvThreshold 40 /* min of 40 chars for auto-detect */
/*
* Decide, based on the contents of the buffer, whether we should do an
* EOL conversion on the data.
*
* We need to decide if we are looking at text data, and if so, what kind
* of line terminator is in use.
*
* If we don't have enough data to make a determination, don't mess with it.
* (Thought for the day: add a "bias" flag, based on the NuRecord fileType,
* that causes us to handle borderline or sub-min-threshold cases more
* reasonably. If it's of type TXT, it's probably text.)
*
* We try to figure out whether it's CR, LF, or CRLF, so that we can
* skip the CPU-intensive conversion process if it isn't necessary.
*
* We will also enable a "high-ASCII" stripper if requested. This is
* only enabled when EOL conversions are enabled.
*
* Returns kConvEOLOff or kConvEOLOn, and sets pFunnel->doStripHighASCII
* if pFunnel->CheckStripHighASCII is set.
*/
static NuValue Nu_DetermineConversion(NuFunnel* pFunnel, const uint8_t* buffer,
uint32_t count)
{
uint32_t bufCount, numBinary, numLF, numCR;
Boolean isHighASCII;
uint8_t val;
if (count < kNuMinConvThreshold)
return kNuConvertOff;
/*
* Check to see if the buffer is all high-ASCII characters. If it is,
* we want to strip characters before we test them below.
*/
if (pFunnel->checkStripHighASCII) {
isHighASCII = Nu_CheckHighASCII(pFunnel, buffer, count);
DBUG(("+++ determined isHighASCII=%d\n", isHighASCII));
} else {
isHighASCII = false;
DBUG(("+++ not even checking isHighASCII\n"));
}
bufCount = count;
numBinary = numLF = numCR = 0;
while (bufCount--) {
val = *buffer++;
if (isHighASCII)
val &= 0x7f;
if (gNuIsBinary[val])
numBinary++;
if (val == kNuCharLF)
numLF++;
if (val == kNuCharCR)
numCR++;
}
/* if #found is > #allowed, it's a binary file */
if (count < 100) {
/* use simplified check on files between kNuMinConvThreshold and 100 */
if (numBinary > kNuMaxUpperASCII)
return kNuConvertOff;
} else if (numBinary > (count / 100) * kNuMaxUpperASCII)
return kNuConvertOff;
/*
* If our "convert to" setting is the same as what we're converting
* from, we can turn off the converter and speed things up.
*
* These are simplistic, but this is intended as an optimization. We
* will blow it if the input has lots of CRs and LFs scattered about,
* and they just happen to be in equal amounts, but it's not clear
* to me that an automatic EOL conversion makes sense on that sort
* of file anyway.
*
* None of this applies if we also need to do a high-ASCII conversion.
*/
if (isHighASCII) {
pFunnel->doStripHighASCII = true;
} else {
if (numLF && !numCR)
pFunnel->convertEOLFrom = kNuEOLLF;
else if (!numLF && numCR)
pFunnel->convertEOLFrom = kNuEOLCR;
else if (numLF && numLF == numCR)
pFunnel->convertEOLFrom = kNuEOLCRLF;
else
pFunnel->convertEOLFrom = kNuEOLUnknown;
}
return kNuConvertOn;
}
/*
* Write a block of data to the appropriate output device. Test for
* excessive data, and raise "outMaxExceeded" if we overrun.
*
* This is either a Funnel function or a DataSink function, depending on
* your perspective.
*/
static inline void Nu_FunnelPutBlock(NuFunnel* pFunnel, const uint8_t* buf,
uint32_t len)
{
Assert(pFunnel != NULL);
Assert(pFunnel->pDataSink != NULL);
Assert(buf != NULL);
Assert(len > 0);
#if 0
if (pFunnel->outMax) {
if (pFunnel->outMaxExceeded)
return;
if (pFunnel->outCount + len > pFunnel->outMax) {
pFunnel->outMaxExceeded = true;
return;
}
}
pFunnel->outCount += len;
#endif
Nu_DataSinkPutBlock(pFunnel->pDataSink, buf, len);
}
/*
* Output the EOL marker requested for this system.
*/
static inline void Nu_PutEOL(NuFunnel* pFunnel)
{
uint8_t ch;
if (pFunnel->convertEOLTo == kNuEOLCR) {
ch = kNuCharCR;
Nu_FunnelPutBlock(pFunnel, &ch, 1);
} else if (pFunnel->convertEOLTo == kNuEOLLF) {
ch = kNuCharLF;
Nu_FunnelPutBlock(pFunnel, &ch, 1);
} else if (pFunnel->convertEOLTo == kNuEOLCRLF) {
ch = kNuCharCR;
Nu_FunnelPutBlock(pFunnel, &ch, 1);
ch = kNuCharLF;
Nu_FunnelPutBlock(pFunnel, &ch, 1);
} else {
Assert(0);
}
}
/*
* Write a buffer of data, using the EOL conversion associated with the
* funnel (if any).
*
* When converting to the system's EOL convention, we take anything
* that looks like an EOL mark and convert it. Doesn't matter if it's
* CR, LF, or CRLF; all three get converted to whatever the system uses.
*/
static NuError Nu_FunnelWriteConvert(NuFunnel* pFunnel, const uint8_t* buffer,
uint32_t count)
{
NuError err = kNuErrNone;
uint32_t progressCount = count;
/*if (pFunnel->outMaxExceeded)
return kNuErrOutMax;*/
if (pFunnel->isFirstWrite) {
/*
* This is the first write/flush we've done on this Funnel.
* Check the data we have buffered to decide whether or not
* we want to do text conversions.
*/
if (pFunnel->convertEOL == kNuConvertAuto) {
pFunnel->convertEOL = Nu_DetermineConversion(pFunnel, buffer,count);
DBUG(("+++ DetermineConversion --> %ld / %ld (%d)\n",
pFunnel->convertEOL, pFunnel->convertEOLFrom,
pFunnel->doStripHighASCII));
if (pFunnel->convertEOLFrom == pFunnel->convertEOLTo) {
DBUG(("+++ Switching redundant converter off\n"));
pFunnel->convertEOL = kNuConvertOff;
}
/* put it where the progress meter can see it */
if (pFunnel->pProgress != NULL)
pFunnel->pProgress->expand.convertEOL = pFunnel->convertEOL;
} else if (pFunnel->convertEOL == kNuConvertOn) {
if (pFunnel->checkStripHighASCII) {
/* assume this part of the buffer is representative */
pFunnel->doStripHighASCII = Nu_CheckHighASCII(pFunnel,
buffer, count);
} else {
Assert(!pFunnel->doStripHighASCII);
}
DBUG(("+++ Converter is on, convHighASCII=%d\n",
pFunnel->doStripHighASCII));
}
}
Assert(pFunnel->convertEOL != kNuConvertAuto); /* on or off now */
pFunnel->isFirstWrite = false;
if (pFunnel->convertEOL == kNuConvertOff) {
/* write it straight */
Nu_FunnelPutBlock(pFunnel, buffer, count);
} else {
/* do the EOL conversion and optional high-bit stripping */
Boolean lastCR = pFunnel->lastCR; /* make local copy */
uint8_t uch;
int mask;
if (pFunnel->doStripHighASCII)
mask = 0x7f;
else
mask = 0xff;
/*
* We could get a significant speed improvement here by writing
* non-EOL chars as a larger block instead of single bytes.
*/
while (count--) {
uch = (*buffer) & mask;
if (uch == kNuCharCR) {
Nu_PutEOL(pFunnel);
lastCR = true;
} else if (uch == kNuCharLF) {
if (!lastCR)
Nu_PutEOL(pFunnel);
lastCR = false;
} else {
Nu_FunnelPutBlock(pFunnel, &uch, 1);
lastCR = false;
}
buffer++;
}
pFunnel->lastCR = lastCR; /* save copy */
}
/*if (pFunnel->outMaxExceeded)
err = kNuErrOutMax;*/
err = Nu_DataSinkGetError(pFunnel->pDataSink);
/* update progress counter with pre-LFCR count */
if (err == kNuErrNone && pFunnel->pProgress != NULL)
pFunnel->pProgress->uncompressedProgress += progressCount;
return err;
}
/*
* Flush any data currently in the funnel.
*/
NuError Nu_FunnelFlush(NuArchive* pArchive, NuFunnel* pFunnel)
{
NuError err = kNuErrNone;
if (!pFunnel->bufCount)
goto bail;
err = Nu_FunnelWriteConvert(pFunnel, pFunnel->buffer, pFunnel->bufCount);
BailError(err);
pFunnel->bufCount = 0;
err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel);
/* fall through with error */
bail:
return err;
}
/*
* Write a bunch of bytes into a funnel. They will be held in the buffer
* if they fit, or flushed out the bottom if not.
*/
NuError Nu_FunnelWrite(NuArchive* pArchive, NuFunnel* pFunnel,
const uint8_t* buffer, uint32_t count)
{
NuError err = kNuErrNone;
/*pFunnel->inCount += count;*/
/*
* If it will fit into the buffer, just copy it in.
*/
if (pFunnel->bufCount + count < kNuFunnelBufSize) {
if (count == 1) /* minor optimization */
*(pFunnel->buffer + pFunnel->bufCount) = *buffer;
else
memcpy(pFunnel->buffer + pFunnel->bufCount, buffer, count);
pFunnel->bufCount += count;
goto bail;
} else {
/*
* Won't fit. We have to flush what we have, and we can either
* blow out what we were just given or put it at the start of
* the buffer.
*/
if (pFunnel->bufCount) {
err = Nu_FunnelFlush(pArchive, pFunnel);
BailError(err);
} else {
err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel);
BailError(err);
}
Assert(pFunnel->bufCount == 0);
if (count >= kNuFunnelBufSize / 4) {
/* it's more than 25% of the buffer, just write it now */
err = Nu_FunnelWriteConvert(pFunnel, buffer, count);
BailError(err);
} else {
memcpy(pFunnel->buffer, buffer, count);
pFunnel->bufCount = count;
}
goto bail;
}
bail:
return err;
}
/*
* Set the Funnel's progress state.
*/
NuError Nu_FunnelSetProgressState(NuFunnel* pFunnel, NuProgressState state)
{
Assert(pFunnel != NULL);
if (pFunnel->pProgress == NULL)
return kNuErrNone;
pFunnel->pProgress->state = state;
return kNuErrNone;
}
/*
* Send a progress update to the application, if they're interested.
*/
NuError Nu_FunnelSendProgressUpdate(NuArchive* pArchive, NuFunnel* pFunnel)
{
NuProgressData* pProgress;
Assert(pArchive != NULL);
Assert(pFunnel != NULL);
pProgress = pFunnel->pProgress;
if (pProgress == NULL)
return kNuErrNone; /* no progress meter attached */
/* don't continue if they're not accepting progress messages */
if (pProgress->progressFunc == NULL)
return kNuErrNone;
/* other than the choice of arguments, it's pretty much the same story */
return Nu_SendInitialProgress(pArchive, pProgress);
}
/*
* Pull the "doExpand" parameter out of the data source.
*/
Boolean Nu_FunnelGetDoExpand(NuFunnel* pFunnel)
{
Assert(pFunnel != NULL);
Assert(pFunnel->pDataSink != NULL);
return Nu_DataSinkGetDoExpand(pFunnel->pDataSink);
}
/*
* ===========================================================================
* NuStraw object
* ===========================================================================
*/
/*
* Allocate and initialize a Straw.
*/
NuError Nu_StrawNew(NuArchive* pArchive, NuDataSource* pDataSource,
NuProgressData* pProgress, NuStraw** ppStraw)
{
NuError err = kNuErrNone;
NuStraw* pStraw = NULL;
Assert(ppStraw != NULL);
Assert(pDataSource != NULL);
pStraw = Nu_Calloc(pArchive, sizeof(*pStraw));
BailAlloc(pStraw);
pStraw->pDataSource = pDataSource;
pStraw->pProgress = pProgress;
pStraw->lastProgress = 0;
pStraw->lastDisplayed = 0;
bail:
if (err != kNuErrNone)
Nu_StrawFree(pArchive, pStraw);
else
*ppStraw = pStraw;
return err;
}
/*
* Free a Straw.
*/
NuError Nu_StrawFree(NuArchive* pArchive, NuStraw* pStraw)
{
if (pStraw == NULL)
return kNuErrNone;
/* we don't own the data source or progress meter */
Nu_Free(pArchive, pStraw);
return kNuErrNone;
}
/*
* Set the Straw's progress state.
*/
NuError Nu_StrawSetProgressState(NuStraw* pStraw, NuProgressState state)
{
Assert(pStraw != NULL);
Assert(pStraw->pProgress != NULL);
pStraw->pProgress->state = state;
return kNuErrNone;
}
/*
* Send a progress update to the application, if they're interested.
*/
NuError Nu_StrawSendProgressUpdate(NuArchive* pArchive, NuStraw* pStraw)
{
NuProgressData* pProgress;
Assert(pArchive != NULL);
Assert(pStraw != NULL);
pProgress = pStraw->pProgress;
if (pProgress == NULL)
return kNuErrNone; /* no progress meter attached */
/* don't continue if they're not accepting progress messages */
if (pProgress->progressFunc == NULL)
return kNuErrNone;
/* other than the choice of arguments, it's pretty much the same story */
return Nu_SendInitialProgress(pArchive, pProgress);
}
/*
* Read data from a straw.
*/
NuError Nu_StrawRead(NuArchive* pArchive, NuStraw* pStraw, uint8_t* buffer,
long len)
{
NuError err;
Assert(pArchive != NULL);
Assert(pStraw != NULL);
Assert(buffer != NULL);
Assert(len > 0);
/*
* No buffering going on, so this is straightforward.
*/
err = Nu_DataSourceGetBlock(pStraw->pDataSource, buffer, len);
BailError(err);
/*
* Progress updating for adding is a little more complicated than
* for extracting. When extracting, the funnel controls the size
* of the output buffer, and only pushes an update when the output
* buffer fills. Here, we don't know how much will be asked for at
* a time, so we have to pace the updates or we risk flooding the
* application.
*
* We also have another problem: we want to indicate how much data
* has been processed, not how much data is *about* to be processed.
* So we have to set the percentage based on how much was requested
* on the previous call. (This assumes that whatever they asked for
* last time has already been fully processed.)
*/
if (pStraw->pProgress != NULL) {
pStraw->pProgress->uncompressedProgress = pStraw->lastProgress;
pStraw->lastProgress += len;
if (!pStraw->pProgress->uncompressedProgress ||
(pStraw->pProgress->uncompressedProgress - pStraw->lastDisplayed
> (kNuFunnelBufSize * 3 / 4)))
{
err = Nu_StrawSendProgressUpdate(pArchive, pStraw);
pStraw->lastDisplayed = pStraw->pProgress->uncompressedProgress;
BailError(err);
}
}
bail:
return err;
}
/*
* Rewind a straw. This rewinds the underlying data source, and resets
* some progress counters.
*/
NuError Nu_StrawRewind(NuArchive* pArchive, NuStraw* pStraw)
{
Assert(pStraw != NULL);
Assert(pStraw->pDataSource != NULL);
pStraw->lastProgress = 0;
pStraw->lastDisplayed = 0;
return Nu_DataSourceRewind(pStraw->pDataSource);
}