nulib2/nufxlibapi.htm
2017-09-21 14:33:49 -07:00

2079 lines
113 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>NufxLib API</title>
<meta http-equiv="Content-Language" content="en-us">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<msnavigation border="0" cellpadding="0" cellspacing="0" width="100%"><msnavigation border="0" cellpadding="0" cellspacing="0" dir="ltr" width="100%"><msnavigation valign="top"><msnavigation border="0" cellpadding="0" cellspacing="0" width="100%">
<meta content="t, default" name="Microsoft Border">
<style type="text/css">
.auto-style1 {
text-decoration: underline;
}
</style>
<link href="main.css" rel="stylesheet" type="text/css" />
</head>
<body bgcolor="#FFFFFF" text="#000000"><!--msnavigation--><table border="0" cellpadding="0" cellspacing="0" width="100%"><tr><td>
<p align="center"><font size="6"><strong>NufxLib API</strong></font><br>
<nobr>[&nbsp;<a href="index.htm" target="">Home</a>&nbsp;]</nobr> <nobr>[&nbsp;<a href="downloads/index.htm" target="">NuLib&nbsp;Downloads</a>&nbsp;]</nobr> <nobr>[&nbsp;<a href="library/index.htm" target="">NuLib&nbsp;Library</a>&nbsp;]</nobr> <nobr>[&nbsp;<a href="nulib2-manual.htm" target="">NuLib2&nbsp;Manual</a>&nbsp;]</nobr> <nobr>[&nbsp;NufxLib&nbsp;API&nbsp;]</nobr> <nobr>[&nbsp;<a href="bugs.htm" target="">Bugs&nbsp;&amp;&nbsp;Features</a>&nbsp;]</nobr></p>
<hr>
</td></tr><!--msnavigation--></table><!--msnavigation--><table border="0" cellpadding="0" cellspacing="0" dir="ltr" width="100%"><tr><!--msnavigation--><td valign="top">
</td></tr></table><msnavigation border="0" cellpadding="0" cellspacing="0" dir="ltr" width="100%"><tr><msnavigation valign="top"><msnavigation border="0" cellpadding="0" cellspacing="0" width="100%"><msnavigation border="0" cellpadding="0" cellspacing="0" width="100%"><tr><msnavigation valign="top">
<h6>&nbsp;</h6>
<h6>NufxLib v3.1.0 API - By Andy McFadden - Last revised 2015/01/09</h6>
<h2><u>Table of Contents</u></h2>
<ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#nufx-overview">NuFX Archive Format Overview</a></li>
<li><a href="#api-overview">API Overview</a></li>
<li><a href="#data-types">Data Types and Source Conventions</a></li>
<li><a href="#read-only">ReadOnly Interfaces</a></li>
<li><a href="#streaming-read-only">StreamingReadOnly Interfaces</a></li>
<li><a href="#read-write">ReadWrite Interfaces</a></li>
<li><a href="#general">General Interfaces</a></li>
<ul>
<li><a href="#archive-ops">Archive Operations</a></li>
<li><a href="#sources">Data Sources</a></li>
<li><a href="#sinks">Data Sinks</a></li>
<li><a href="#callbacks">Callbacks</a></li>
</ul>
<li><a href="#helper">Helper Functions</a></li>
<li><a href="#tables">Tables</a>
</li>
<li><a href="#commentary">Additional Commentary</a>
<ul>
<li><a href="#replace-existing">Replacing Existing Records and Files</a></li>
<li><a href="#shrinkit">ShrinkIt Compatibility Mode</a></li>
<li><a href="#compression-formats">Compression Formats</a></li>
</ul>
</li>
<li><a href="#porting">Porting</a></li>
<li><a href="#design-notes">Design Notes</a></li>
<li><a href="#history">Version History</a></li>
<li><a href="#acknowledgements">Acknowledgements</a></li>
</ul>
<h2><a name="introduction"></a><u>Introduction</u></h2>
<p>NuFX, short for &quot;New File Exchange&quot;, is a file format developed by
Andy Nicholas for archiving files and disks on the Apple II series of
computers.&nbsp; The format was devised in tandem with the
development of ShrinkIt, which became the standard archive software for the
Apple II soon after it's release in 1989.&nbsp; NuFX archives usually have filenames that end in &quot;.SHK&quot;.</p>
<p>This document describes the API (Application Program Interface) for NufxLib,
a library of functions that manipulate NuFX archives.</p>
<p>Good engineering practices dictate that an API should be minimal and complete.&nbsp; The
confusion generated by redundant and overlapping interfaces can be as harmful as
an omitted vital feature.&nbsp; I feel pretty good about the
&quot;complete&quot; part, since NufxLib provides a way to do pretty much
everything that I can reasonably expect somebody to want to do, but in some
cases &quot;minimal&quot; has been swept aside in the name of convenience.&nbsp;
(See the <a href="#design-notes">Design Notes</a> section for additional
commentary on this topic.)</p>
<p>The NuFX specification is extremely general, and does not explicitly allow or
forbid unusual conditions like having a record with two filenames in it.&nbsp;
NufxLib follows the <a href="library/FTN.e08002.htm"> NuFX specification</a> on everything that is spelled out, but
restricts some of the undefined behaviors to the subset defined in the <a href="library/nufx-addendum.htm">NuFX
Addendum</a>.</p>
<p>In this document, the term &quot;threads&quot; usually refers to NuFX
threads -- structures in the archive -- not CPU threads.</p>
<h3>Goals</h3>
<ul>
<li>Provide a complete set of function calls for manipulating NuFX archives.</li>
<li>Be portable.&nbsp; Keep most of the system-dependent code in the application rather than the
library, and use plain old ANSI C.</li>
<li>Define the functions so that command-line, GUI, and embedded applications
can be developed easily.</li>
<li>Obey &quot;nice library&quot; semantics.&nbsp; No globals, no
&quot;static&quot; local variables, avoid non-reentrant libc calls,
etc.&nbsp; It should be possible for the application to have more than one
NuFX archive open at a time, and be able to perform operations on both
simultaneously.</li>
<li>Be efficient.&nbsp; Don't allocate unneeded memory, don't waste time
reading and writing unnecessarily, and don't assume that archives are small
relative to other system resources.</li>
<li>When in doubt, do what ShrinkIt does.&nbsp; There are aspects of the NuFX
file format (notably regarding Threads) where some things aren't well
defined.&nbsp; Emulating ShrinkIt avoids compatibility problems.</li>
</ul>
<h3>Not Goals</h3>
<ul>
<li>Don't try to provide an interface that works for all archive
formats.&nbsp; NuFX is a very complex format, developed for a specific
platform, and attempting to describe it and other archive formats in a
general way would result in a big mess.&nbsp; It is assumed
that anyone using this library has at least a passing familiarity with the
NuFX file format, so instead of passing around generalized structures, the
application gets full read access to all fields in Records, Threads, and the
Master Header.</li>
<li>Don't try to provide everything an Apple II archiver needs.&nbsp; Binary
II and BinSCII support are useful, but they don't need to be part of the NufxLib
library.</li>
<li>Don't do things the application should be doing.&nbsp; Recursive directory
tree traversals are system-dependent and may not even be desirable.&nbsp;
Apple II disk images come in a plethora of formats, but there's no need for
the library to understand them all.&nbsp; And things like filesystem-dependent
filename remapping are best left to the application.</li>
</ul>
<p>That explains what I set out to do.&nbsp; Here's a quick summary of what I
accomplished.</p>
<h3>Features</h3>
<ul>
<li>Full set of calls for adding, extracting, listing, updating, renaming,
deleting, and testing Records and Threads.</li>
<li>All write operations are queued up and deferred until a &quot;flush&quot;
call is issued, then executed in the most efficient manner possible.</li>
<li>Automatically handles Binary II and GSHK Self-Extracting Archive
wrappers.&nbsp; BXY, SEA, and BSE files are handled as easily as SHK.&nbsp;
Can also skip over junk left at the start of the file, such as MacBinary
headers.</li>
<li>Supports compression and expansion with the ShrinkIt LZW/1 and LZW/2
algorithms, the SQueeze algorithm used by BLU (and nominally supported by
ShrinkIt), LZC (i.e LZW in UNIX &quot;compress&quot;), and optionally zlib's
&quot;deflate&quot; algorithm and libbz2's BWT+Huffman.</li>
<li>Can (optionally) update an archive in place (i.e. without writing to and
later renaming a
temp file) if the only operations are limited to pre-sized thread updates
and/or the addition of new files.</li>
<li>Can perform most of the read-only operations on a &quot;streaming&quot;
archive, i.e. one where the archive is presented as a pipe or shell
redirect.</li>
<li>Data can be added from or extracted to a file on disk, FILE*, or memory
buffer.</li>
<li>End-of-line conversions can be performed during extraction.&nbsp; Text
files can be detected automatically or identified explicitly.</li>
<li>Has no hard-coded size restrictions other than those imposed by C library
functions.&nbsp; (It
has some &quot;reasonableness&quot; tests based on compile-time constants,
but they're set pretty high and are easy to change.&nbsp; They're intended
to spot corrupt archives, not define limits.)</li>
</ul>
<p>The library is protected by copyright, but can be distributed under the terms
of theBSD License.&nbsp; See the file &quot;COPYING-LIB&quot; for
full details.</p>
<h3>Interface changes from v1.x to v2.x</h3>
<p>Some changes were made during the development of NufxLib that broke binary
compatibility with version 1.1 of the library.&nbsp; The changes were:</p>
<ul>
<li>NufxLib no longer call free() or fclose() on resources allocated by the
application.&nbsp; (This was done for some DataSource objects that were part
of &quot;bulk&quot; adds and would've been inconvenient for the application
to manage.)&nbsp; Instead, the application passes a pointer to a
&quot;resource release&quot; function that will be called when
appropriate.&nbsp; This makes Win32 DLL implementations more likely to work,
and allows C++ applications to use customized &quot;new&quot; and
&quot;delete&quot;.&nbsp; If your application creates DataSource objects, it
will need to change the order of some of the arguments.</li>
<li>Callback setters no longer return NuError.&nbsp; They now return the
previous callback function.</li>
<li>A new progress state, kNuProgressAborted, was added so the progress
updater can correctly handle a &quot;cancel&quot; button.</li>
<li>A new field, intended to allow applications to access the original
filename in &quot;progress&quot; and &quot;error&quot; callbacks, has been
added to NuFileDetails and NuErrorStatus.</li>
</ul>
<p>In addition, a NuTestRecord call was added.</p>
<p>Applications written against v1.x may need to be updated.&nbsp; Check the
NufxLib &quot;samples&quot; directory for examples of programs that use the
updated calls.</p>
<p>To make version management easier, v2.x includes the version number in the
NufxLib.h header file.&nbsp; This allows dynamically-linked applications to
compare a &quot;compiled&quot; version against a &quot;linked&quot; version.</p>
<h3>Interface changes from v2.x to v3.x</h3>
<p>This was a major source code cleanup effort, one aspect of which was
switching from general C types (&quot;unsigned long&quot;) to types with explicit sizes
(&quot;uint32_t&quot;).&nbsp; In some cases this caused some compilers to report errors,
even though there's a fair chance that binary compatibility wasn't affected.&nbsp;
Since it was an API-breaking change at some level, the major version number was
bumped.</p>
<p>The other major API change was the separation of Mac OS Roman and Unicode
strings, which were previously blended freely.</p>
<hr>
<h2><a name="nufx-overview"></a><u>NuFX Archive Format Overview</u></h2>
<p>This document assumes that you are already familiar with the NuFX archive
format, as described in the <a href="library/FTN.e08002.htm">Apple II File Type
Note for $e0/8002</a> and the Winter 1990 issue of Call-A.P.P.L.E.&nbsp; For those unwilling to wade through the technical
documentation, here is a quick overview.</p>
<p>A NuFX archive is composed of a Master Header followed by a series of
Records.&nbsp; Each Record is composed of a Record Header and one or more Threads.&nbsp; The general
idea is to store one file per Record.</p>
<p>Each Thread holds a blob of data.&nbsp; The data can be a data or resource
fork of a file, a disk image, a comment, or the filename for the
Record.&nbsp; The Threads are identifed by a &quot;class&quot; and a
&quot;kind&quot;.&nbsp; The &quot;class&quot; tells you if it's a data thread,
comment, filename, or something else, and the &quot;kind&quot; refines the
class.&nbsp; For example, the resource fork of a file is a data-class thread with a
&quot;kind&quot; of 2.</p>
<p>Some Threads, notably filenames and comments, are pre-sized, meaning that the
space allocated for them in the archive is larger than what is actually
used.&nbsp; Filenames usually have at least 32 bytes set aside for them, though
in practice a simple ProDOS filename will be shorter.&nbsp; This makes it
possible to rename files and update comments without having to reconstruct the
archive.</p>
<p>The archive Master Header has only a few bits of information, such as the
number of records and the date the archive was created.&nbsp; Unlike a ZIP
archive, NuFX has no central table of contents.&nbsp; If you want to display the
contents of an archive, you have to read the first Record header, pull the
filename out (usually by finding and reading a filename Thread), compute the total
size of the Record, and seek forward past the data.&nbsp; Repeat the
process with each subsequent record, until you reach the end of the archive.&nbsp;</p>
<p>The predominant compression algorithm is a slightly modified LZW (Ziv-Lempel-Welch).&nbsp;
It's
fast, but not very effective compared to the standard methods used in modern
archivers.</p>
<h2><a name="api-overview"></a><u>API Overview</u></h2>
<p>There are five basic categories of API calls. </p>
<p><b>ReadOnly</b> calls do not modify the archive in any way.&nbsp; The
operations include things like listing and extracting files.&nbsp; These can be
used on archive files opened read-only or read-write.</p>
<p><b>StreamingReadOnly</b> calls are a subset of ReadOnly calls that can be
made on a streaming archive.&nbsp; A &quot;streaming&quot; archive is one that
cannot be seeked, e.g. an archive being received over a network socket or a pipe
from stdin.&nbsp; The same functions are invoked as for ReadOnly archives, but
in some rare cases the results may be different.</p>
<p><b>ReadWrite</b> calls change the archive contents.&nbsp; Functions that add
and delete files are here.&nbsp; These can only be used on archive files opened
read-write.</p>
<p><b>General</b> calls can be made regardless of how the archive was
opened.&nbsp; Functions included here can get and set archive parameters and
define callbacks.</p>
<p><b>Helper</b> functions don't do anything to the archive.&nbsp; They're functions
or macros that do useful things with some of the data types returned.&nbsp;
They're included as a convenience.</p>
<p>The library does everything it can to aid multi-threading.&nbsp; You should
be able to perform simultaneous operations on multiple archives (assuming you
have the reentrant versions of certain libc calls available).&nbsp; You cannot,
however, invoke multiple simultaneous operations on a single archive.</p>
<p>There is a general philosophy of laziness employed.&nbsp; For example,
opening an archive does not cause the entire table of contents to be read.&nbsp;
(In a NuFX archive, that would require scanning through most of the file.)&nbsp;
As a result, there are actually three different ways to get the table of
contents out of an archive:</p>
<ol>
<li>Read-as-you-go, forgetful.&nbsp; On StreamingReadOnly files, we handle an
individual record and then throw the data away.&nbsp; We can't seek back to
deal with the record again, so there's no point in holding onto it.&nbsp;
This feature allows applications with very limited memory to list the
contents of and extract files
from very large archives.</li>
<li>Read-as-you-go.&nbsp; While performing a whole-archive operation (e.g.
getting the complete list of contents, or extracting all files) on a
non-streaming file, we read and save the table of contents as we go.&nbsp;
This saves us from having to scan through the archive once to get the
contents, and then running through it again to extract the files.&nbsp; This
might seem silly, but if you're extracting from an archive on a slow medium
(e.g. floppy or a sluggish CD-ROM drive), it cuts
the time required nearly in half.</li>
<li>Read up front.&nbsp; For operations like single-file extractions and
anything in the ReadWrite category, we want to have full knowledge of the
archive up front.&nbsp; There are cases where this will be less efficient
than a more cleverly designed algorithm, but it's much simpler this way.</li>
</ol>
<p>For write operations, a certain form of laziness is again employed.&nbsp; If
you want to delete three records from various points in an archive, you don't
want to have to update the archive three times.&nbsp; NufxLib handles this by
deferring all write operations until a &quot;flush&quot; call is made.&nbsp; In most cases, a
&quot;flush&quot; results in a new archive being constructed in a temp file,
which is subsequently renamed over the original.&nbsp; The flush call does not
close the archive, so it is possible to do things like:</p>
<font size="-1">
<ol>
<li>Open the archive.</li>
<li>Queue up a bunch of operations.</li>
<li>Flush changes.</li>
<li>Queue up some more operations.</li>
<li>Abort changes.</li>
<li>Queue up yet more operations.</li>
<li>Flush changes.</li>
<li>Close archive.</li>
</ol></font>
<p>In certain restricted cases, such as updating a comment or appending new
records,
the original archive can (optionally) be updated in place, saving a potentially
lengthy copying of data.</p>
<p>As a final example of laziness, NufxLib does not re-read the archive it has
just written after a Flush.&nbsp; It would have been easier to write all
changes, throw out all data structures, and re-read the archive from scratch,
but that could be slow.&nbsp; Instead, the library keeps track of the changes it
has made -- something that gets a little tricky when filename threads are
updated.&nbsp; Being lazy is often more work.</p>
<p>Filenames stored in archives use the Mac OS Roman character set.&nbsp; The
low 128 characters are ASCII, the high 128 are specified
<a href="http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT">here</a>.&nbsp;
NufxLib will convert between Mac OS Roman and Unicode when necessary, and
provides conversion functions for application use.</p>
<p>When specifying a &quot;local filename&quot;, i.e. a file on Linux or Windows, the API
expects a Unicode string.&nbsp; When referring to an archived file by name (the
&quot;storage name&quot;), the API uses the Mac OS Roman form.&nbsp; The parameter and
field names reflect the character set (&quot;UNI&quot; or &quot;MOR&quot;), and use the UNICHAR type
for Unicode strings.&nbsp; On Linux and Mac OS X the filename is encoded with
UTF-8.&nbsp; On Windows it should be encoded with UTF-16, but that hasn't been
implemented yet, so the API still uses 8-bit characters and effectively treats
MOR strings as if they were Windows Code Page 1252.&nbsp; (This means the
behavior of NufxLib is essentially unchanged for 3.0 on Windows.)</p>
<h2><a name="data-types"></a><u>Data Types and Source Conventions</u></h2>
<p>All API calls and data types begin with &quot;Nu&quot;, and all constants start with &quot;kNu&quot;.&nbsp;
All internal functions start with &quot;Nu_&quot;, and any internal data tables with
global scope start with &quot;gNu&quot;.&nbsp; Hopefully these rules will avoid
compile-time and link-time name conflicts.</p>
<p>For details about the fields available in different structures, see the
NufxLib.h header file.&nbsp; Everything in NufxLib.h is
public.&nbsp; Most of these types have a direct analog with a
field or structure in the NuFX specification.</p>
<p><strong>UNICHAR</strong> (char -or- wchar_t): All filenames for &quot;local&quot;
files, i.e. files on the Linux or Windows filesystem, should use UNICHAR.&nbsp;
This will be <code>char</code> on Linux and Mac OS X.&nbsp; Someday it will be
<code>wchar_t</code> for Win32, but for now it's an 8-bit char there as well.</p>
<p>Windows uses UTF-16 encoding, so <code>wchar_t</code> is required.&nbsp;
(Unicode filename handling for Windows is incomplete, so the code does not
currently use wide chars.)</p>
<p><b>NuError</b> (enum): Most library functions return NuError.&nbsp; A value
of zero (kNuErrNone) indicates success, anything else indicates failure.&nbsp;
Values less than zero are NufxLib errors, while values greater than zero are
system errors (like ENOENT).</p>
<p><b>NuResult</b> (enum): Callback functions return these values to tell
NufxLib how things went.&nbsp; For example, an error callback can tell the
library to Abort, Retry, or Skip.&nbsp; (Okay, it can Ignore too.)</p>
<p><b>NuRecordIdx</b> and <b>NuThreadIdx</b> (uint32_t): These are used to
identify a specific record or thread in API calls.&nbsp; Their values are assigned when the archive file is
read.&nbsp; They aren't reused, so if you delete some records and add some new
ones, the indices of the deleted records won't appear again.&nbsp; Do not assume
that the indices start at a specific value or are assigned in a particular
order.&nbsp; The indices are assigned when the archive is opened, and if you
close and reopen the archive, they may be completely different.</p>
<p><b>NuThreadID</b> (uint32_t): This is a combination of the 16-bit
&quot;thread class&quot; and the 16-bit &quot;thread kind&quot;.&nbsp; Constants
are defined for common values, e.g. kNuThreadIDDataFork (0x00020000) indicates a data fork.</p>
<p><b>NuThreadFormat</b> (enum): An enumeration of constants representing the
16-bit &quot;thread format&quot; value.&nbsp; This is used to specify a type of
compression (uncompressed, LZW/1, LZW/2, etc).</p>
<p><b>NuFileSysID</b> (enum): An enumeration of GS/OS file system identifiers.</p>
<p><b>NuStorageType</b> (enum): An enumeration of ProDOS storage types.&nbsp;
There are extended (forked) files, directories, and three types of plain files.</p>
<p><b>NuArchive</b> (opaque struct): This is the fundamental state structure for
all API calls.&nbsp; Every call takes one of these as an argument.&nbsp; The
structure contains all of the information about the archive and pending
operations.</p>
<p><b>NuCallback</b> (pointer to function): Callback function declarations must
match this type.&nbsp; An example would be &quot;NuResult MyFunction(NuArchive* pArchive, void* args)&quot;.</p>
<p><b>NuValueID</b> (enum): An identifier for settable values.&nbsp; You can
change certain NufxLib parameters after opening an archive.&nbsp; This enum is
how you specify which parameter you want to change.</p>
<p><b>NuValue</b> (uint32_t): The new value for the parameter specified by
the NuValueID.</p>
<p><b>NuAttrID</b> (enum): An identifier for archive attributes.&nbsp; You can
get information about archive attributes (characteristics of the archive itself)
through a NufxLib interface.&nbsp; This type has an enumeration of the legal
values.</p>
<p><b>NuAttr</b> (uint32_t): The value for the attribute specified by the
NuAttrID is placed in one of these.</p>
<p><b>NuDataSource</b> (opaque struct): Some of the fancier NufxLib calls allow
you to use data from a file on disk, a file that's already open, or a buffer of
memory.&nbsp; This struct contains that specification.</p>
<p><b>NuDataSink</b> (opaque struct): Like NuDataSource, this specifies a data
location.&nbsp; This struct is for data being extracted.</p>
<p><b>NuDateTime</b> (struct): This holds the date and time in an expanded
format, using the same structure as TimeRec from &quot;misctool.h&quot; on the
IIgs.</p>
<p><b>NuThread</b> (struct): The fields from the thread header, as well as a few
new ones like the absolute file offset, are accessible.</p>
<p><b>NuRecord</b> (struct): This has all of the fields from the NuFX Record
structure, as well as some convenience fields (like &quot;filename&quot;, which
always points to the right filename whether it was stored in the record header
or came out of a thread).&nbsp; Some calls cause a NuRecord structure to be
passed to a callback function, where it can be accessed directly.&nbsp; The
Threads are represented as an array of NuThread structures attached to the
NuRecord.</p>
<p><b>NuMasterHeader</b> (struct): This holds the data from the archive's master
header block.</p>
<p><b>NuRecordAttr</b> (struct): Some of the fields in a NuRecord can be
changed, such as the file type and modification date.&nbsp; This structure
contains the modifiable fields, and is used as an argument to two of the API
calls.</p>
<p><b>NuFileDetails</b> (struct): When adding files, it is up to the application
to supply many of the details about the file, such as the file type, access
permissions, and modification date.&nbsp; This structure provides a way to pass
those values into the library.</p>
<p><b>NuSelectionProposal</b> (struct): Selection callback functions receive one
of these.</p>
<p><b>NuPathnameProposal</b> (struct): Pathname filter callback functions
receive one of these.</p>
<p><b>NuProgressData</b> (struct): Progress update callback functions receive
one of these.</p>
<p><b>NuProgressState</b> (enum): A component of NuProgressData, this tells the
callback function what the library is doing.</p>
<p><b>NuErrorStatus</b> (struct): Error handling callback functions receive one
of these.</p>
<p>Files are referenced with standard libc <b>FILE*</b> pointers.&nbsp; The
library uses fseek and ftell, which are defined by POSIX to take a signed long
integer for the offset argument, so archives larger than 2GB cannot be handled.</p>
<hr>
<h2><a name="read-only"></a><u>ReadOnly Interfaces</u></h2>
<p>These interfaces can be used on read-only and read-write archives.&nbsp; A
subset, described later, can also be used on streaming-read-only archives.</p>
<h4>NuError <u>NuOpenRO</u>(const UNICHAR* archivePathnameUNI, NuArchive** ppArchive)</h4>
<p>Creates a new NuArchive structure for the &quot;archivePathname&quot;
file.&nbsp; The file will be opened in read-only mode.</p>
<p>Attempting to use ReadWrite interfaces on a read-only archive will fail.</p>
<h4>NuError <u>NuContents</u>(NuArchive* pArchive, NuCallback contentFunc)</h4>
<p>Read the list of entries from the archive.&nbsp; If the full table of
contents has already been read, the in-memory copy will be used.</p>
<p>&quot;contentFunc&quot;
is a callback function that will be called once for every record in the archive.&nbsp;
The callback function should look something like this:</p>
<blockquote>
<p>NuResult EntryListing(NuArchive* pArchive, const NuRecord* pRecord)</p>
</blockquote>
<p>(Depending on your compiler, you may have to declare &quot;pRecord&quot; as a void*
and cast it in the function.)</p>
<p>The record passed to the callback function does not reflect the results of
any un-flushed changes.&nbsp; Additions, deletions, and updates will not be
visible until NuFlush is called.</p>
<p>The application must not attempt to retain a copy of &quot;pRecord&quot;
after the callback returns, as the structure may be freed.&nbsp; Anything of interest should be copied out.</p>
<h4>NuError <u>NuExtract</u>(NuArchive* pArchive)</h4>
<p>Try to extract all files from the archive.&nbsp; Each entry is passed through
the SelectionFilter callback, if one has been supplied, to determine whether or
not it should be extracted.&nbsp; The OutputPathnameFilter callback is invoked
to covert the filenames to something appropriate for the target filesystem.</p>
<p>On systems that support forked files, a record with both data and resource
forks can be extracted to the individual forks of the same file.&nbsp; On
systems without native support for forks, the data can be extracted into two
different files by using the OutputPathnameFilter.&nbsp; If the system doesn't
support forks, and no OutputPathnameFilter is specified, then the forks will be
extracted into the same file.&nbsp; Depending on the value of the
kNuValueHandleExisting parameter, this could result in one fork overwriting the
other, in one fork not getting extracted, or in the HandleError callback getting
invoked.&nbsp; (The HandleError callback can choose to rename the file,
overwrite it, skip the current entry, or abort the entire process.)</p>
<p>The global EOL conversion setting is applied to all threads, but is
automatically turned off for disk image threads.</p>
<h4>NuError <u>NuExtractRecord</u>(NuArchive* pArchive, NuRecordIdx recordIdx)</h4>
<p>Extract a single record.&nbsp; Otherwise identical to NuExtract.&nbsp; The
SelectionFilter callback, if specified, will be invoked.</p>
<p>There are a number of ways to get the recordIdx.&nbsp; You can call
NuContents and use the callback to find the one you want.&nbsp; You can get the recordIdx by the filename stored in the
archive, with
NuGetRecordIdxByFilename.&nbsp; Or, you can get it by the record's offset in the archive,
using NuGetRecordIdxByPosition.</p>
<h4>NuError <u>NuExtractThread</u>(NuArchive* pArchive,
NuThreadIdx threadIdx, NuDataSink* pDataSink)</h4>
<p>Extract a single thread.&nbsp; Specify the thread index and a place to put
the data.&nbsp; The SelectionFilter callback, if specified, will be invoked.</p>
<p>Remember that, if EOL conversion is enabled in the data sink, the amount of data that comes
out of a thread may not match pThread's &quot;actualThreadEOF&quot; value.</p>
<p>(In some ways it doesn't really make sense to call the SelectionFilter callback
when a specific thread has been singled out for extraction.&nbsp; However, it's
easy to disable (set the callback to NULL), it may prove useful, and it keeps
the interface consistent.)</p>
<h4>NuError <u>NuTest</u>(NuArchive* pArchive)</h4>
<p>The NuTest call is functionally equivalent to NuExtract in every way but one:
it doesn't actually extract anything.&nbsp; If you want to test a subset of the
files, supply a SelectionFilter callback.</p>
<p>This won't test filenames or comments because those aren't extracted by
NuExtract.&nbsp; However, since such threads don't have CRCs, there's really
nothing to test anyway.&nbsp; The parts that can be tested for correctness are
verified automatically when the archive table of contents is read.</p>
<h4>NuError <u>NuTestRecord</u>(NuArchive* pArchive, NuRecordIdx recordIdx)</h4>
<p>A single-record version of NuTest.</p>
<h4>NuError <u>NuGetRecord</u>(NuArchive* pArchive, NuRecordIdx recordIdx,
const NuRecord** pRecord)</h4>
<p>Get a pointer to the record header.&nbsp; The thread array can be accessed
through this pointer.</p>
<p>As with callbacks, when you get a const pointer, it is very important that
you don't try to modify it.&nbsp; The structure pointed to is part of the
current archive state, so the effects of changes are unpredictable.&nbsp; If you
wish to alter fields in the Record header, use the NuSetRecordAttr call.</p>
<p>IMPORTANT: you must discard this pointer if you call NuFlush or NuClose.</p>
<h4>NuError <u>NuGetRecordIdxByName</u>(NuArchive* pArchive, const char* nameMOR,
NuRecordIdx* pRecordIdx)</h4>
<p>Get the recordIdx for the first record in the archive whose case-insensitive
filename matches &quot;name&quot;.&nbsp; The value retrieved can be used with
any call that takes a NuThreadIdx argument.</p>
<p>The &quot;name&quot; string must match the record's filename exactly,
including the filename separator character.</p>
<p>If you know what you want to extract from an archive by name, use this.</p>
<h4>NuError <u>NuGetRecordIdxByPosition</u>(NuArchive* pArchive, uint32_t position, NuRecordIdx* pRecordIdx)</h4>
<p>Get the recordIdx for nth record in the archive.&nbsp; &quot;position&quot;
is zero-based, meaning the very first record in the archive is at position 0,
the next is at position 1, and so on.&nbsp; The value retrieved can be used with
any call that takes a NuRecordIdx argument.</p>
<p>This could be useful when an application is certain that it is only
interested in the very first record in the archive, e.g. an Apple II emulator
opening a disk image.</p>
<hr width="50%">
<h2><a name="streaming-read-only"></a><u>StreamingReadOnly Interfaces</u></h2>
<p> A streaming
archive is presented to the library as a FILE* that can't be seeked, generally
because it was handed to the application via a pipe or shell redirect.&nbsp; A
subset of the ReadOnly interfaces are supported.&nbsp; All of them leave the
stream pointed at the first byte past the end of the archive.</p>
<p> This calls are also useful for files on disk in situations where memory is
at a premium.&nbsp; Because it's impossible to seek backwards in the archive, no
attempt is made to remember anything about records other than the one most
recently read.</p>
<p>The interfaces supported are:</p>
<dl>
<dt><b><u>NuContents</u></b>
<dd>Behaves just like the non-streaming version.
<dt><u><b>NuTest</b></u></dt>
<dd>Behaves just like the non-streaming version.</dd>
<dt><u><b>NuExtract</b></u></dt>
<dd>Behaves like the non-streaming version for well-formed archives.&nbsp; If a filename is stored in a thread, and the filename thread comes after a data
thread, NufxLib would need to extract the data before it knows what filename to
use.&nbsp; This currently results in an error.</dd>
</dl>
<p>There is one interface that only applies to StreamingReadOnly archives:</p>
<h4>NuError <u>NuStreamOpenRO</u>(FILE* infp, NuArchive** ppArchive)</h4>
<p>Creates a new NuArchive structure for &quot;infp&quot;.&nbsp; The file must
be positioned at the start of the archive.</p>
<p>It should be possible to concatenate multiple archives together, and use them
by issuing consecutive NuStreamOpenRO calls.</p>
<p>If your system requires fopen(filename, &quot;rb&quot;) instead of
&quot;r&quot; (e.g. Win32), make sure the archive file was opened with
&quot;b&quot;, or you may get &quot;unexpected EOF&quot; complaints.</p>
<hr width="50%">
<h2><a name="read-write"></a><u>ReadWrite Interfaces</u></h2>
<h4>NuError <u>NuOpenRW</u>(const UNICHAR* archivePathnameUNI, const UNICHAR*
tempPathnameUNI, uint32_t flags, NuArchive** ppArchive)</h4>
<p>Open a file for read-write operations.&nbsp; A pointer to the new archive is
returned via &quot;ppArchive&quot;.</p>
<p>&quot;archivePathnameUNI&quot; is the name of the archive to open.&nbsp; If the
file has zero length, the archive will be treated as if NufxLib had just created
it.</p>
<p>&quot;tempPathnameUNI&quot; is the name of the temp file to use.&nbsp; The call
will fail if the temp file already exists.&nbsp; The temp file must be in a
location that allows it to be renamed over the original archive when a
&quot;flush&quot; operation has completed.&nbsp; If &quot;tempPathname&quot;
ends in six 'X's, e.g. &quot;tmpXXXXXX&quot;, the name will be treated as a
mktemp-style pattern, and a unique six-character string will be substituted
before the file is opened.&nbsp; Note that the temp file will be opened
even if &quot;kNuValueModifyOrig&quot; is set.</p>
<p>&quot;flags&quot; is a bit vector of boolean flags that affect how the
archive is opened.&nbsp; If no flags are set, and the archive doesn't exist, the
call will fail.&nbsp; If &quot;kNuOpenCreat&quot; is set, the archive will be
created if it doesn't exist.&nbsp; If &quot;kNuOpenCreat&quot; and &quot;kNuOpenExcl&quot;
are both set, the call will fail if &quot;archivePathname&quot; already exists
(i.e. the archive *must* be created).</p>
<p>If the archive was just created, &quot;kNuValueModifyOrig&quot; will be set
to &quot;true&quot;.</p>
<p>NufxLib can tell the difference between a BXY file (NuFX in a Binary II
wrapper) and a BNY file with several entries whose first entry happens to be a NuFX
archive.&nbsp; Access to BNY files that happen to have a ShrinkIt archive in
them isn't supported.</p>
<h4>NuError <u>NuFlush</u>(NuArchive* pArchive, long *pStatusFlags)</h4>
<p>Commits all pending write operations.</p>
<p>&quot;pStatusFlags&quot; gets a bit vector of flags regarding the status of
the archive.&nbsp; If a non-kNuErrNone result is returned, &quot;pStatusFlags&quot;
may contain one or more of the following:</p>
<ul>
<li>kNuFlushSucceeded - the flush succeeded, but something failed after the
archive was written (e.g. the archive file couldn't be removed, or the temp
file couldn't be renamed over the original)</li>
<li>kNuFlushAborted - the flush failed, and all of your changes were thrown
out</li>
<li>kNuFlushCorrupted - a flush to the original archive file failed, possibly
resulting in corruption to the archive (kNuValueModifyOrig must be set for
this to happen)</li>
<li>kNuFlushReadOnly - because of the failure, the archive has been placed
into read-only mode</li>
<li>kNuFlushInaccessible - because of the failure, the archive is no longer
accessible, and should be closed (can happen if we successfully rename the
temp file over the original, but can't reopen the archive)</li>
</ul>
<p>Some of the above are mutually exclusive, e.g. only one of kNuFlushSucceeded,
kNuFlushAborted, and kNuFlushCorrupted will be set.</p>
<p>Any records without threads -- either created that way or having had all
threads deleted -- will be removed.&nbsp; Newly-created records without filename
threads will have one added.&nbsp; (Existing records without filenames are
frowned upon but left alone.)</p>
<p>Normally, the archive is reconstructed in the temp file, and the temp file is
renamed over the original archive after all of the operations have completed
successfully.&nbsp; As a performance optimization, if kNuValueModifyOrig is
&quot;true&quot;, NuFlush will try to modify the archive in place.&nbsp; This is
only possible if the changes made to the archive consist entirely of additions of new
files, updates to pre-sized threads, and/or setting record attributes.&nbsp; If
other changes have been made, the update will be done through the temp file.</p>
<p>If an operation fails during the flush, all changes will be aborted.&nbsp; If
something fails in a way that can't be recovered from, such as failing to rename
the temp file after a successful flush or failing partway through an update to
the original archive, the archive may be switched to read-only mode to prevent
future operations from compounding the problem.</p>
<h4>NuError <u>NuAddRecord</u>(NuArchive* pArchive, const NuFileDetails*
pFileDetails, NuRecordIdx* pRecordIdx)</h4>
<p>Add a new record with no threads.&nbsp; The index of the created record is
returned in &quot;pRecordIdx&quot;.&nbsp; This always creates a &quot;version
3&quot; record, and expects that the filename will be stored in a thread.</p>
<p>&quot;pFileDetails&quot; is a pointer to a NuFileDetails structure.&nbsp;
This contains most of the interesting fields in a record, such as access flags,
dates, file types, and the filename.&nbsp; The &quot;threadID&quot; field is
ignored for this call.</p>
<p>&quot;pRecordIdx&quot; may be NULL.&nbsp; However, the only way to add
threads to the record is with NuAddThread, which requires the record index as a
parameter, so you almost certainly want to get this value.</p>
<p>If no filename thread is added, the NuFlush call will use the &quot;storageName&quot;
field from the &quot;pFileDetails&quot; parameter to create a filename thread
for it.</p>
<p>If no threads are added at all, the NuFlush call will throw the record away.</p>
<p>The &quot;pFileDetails-&gt;storageName&quot; may not start with the filename separator
argument, e.g. &quot;/tmp/foo&quot; is illegal but &quot;tmp/foo&quot; is okay.</p>
<p>If a disk image thread is added to the record, and the &quot;storageType&quot;
and &quot;extraType&quot; values set by &quot;pFileDetails&quot; aren't
compatible, the entries will be replaced
with values appropriate for the thread.&nbsp; For records with non-disk data-class
threads, the storageType will be adjusted when necessary.</p>
<p>Depending on the values of kNuValueAllowDuplicates and kNuValueHandleExisting,
this may replace an existing record in the archive.&nbsp; See <a href="#replace-existing">Replacing
Existing Records and Files</a> for details.</p>
<h4>NuError <u>NuAddThread</u>(NuArchive* pArchive, NuRecordIdx recordIdx,
NuThreadID threadID, NuDataSource* pDataSource, NuThreadIdx* pThreadIdx)</h4>
<p>Add a new thread to a record.&nbsp; You may add threads to an existing record
or a newly created one.&nbsp; Some combinations of threads are not allowed, and
will cause an error to be returned.&nbsp; (See the <a href="library/nufx-addendum.htm">NuFX
Addendum</a> for details.)</p>
<p>&quot;recordIdx&quot; is the index of the record being added to.</p>
<p>&quot;threadID&quot; is the class and kind of the thread being added.&nbsp;
This defines how the data is labeled in the archive, and whether the contents of
&quot;pDataSource&quot; are to be regarded as pre-sized or not.</p>
<p>&quot;pDataSource&quot; is where the data comes from.&nbsp; If the source is
uncompressed, the thread will be compressed with the compression value currently
defined by kNuValueDataCompression.&nbsp; (You can set the value independently
for each call to NuAddThread.)&nbsp; Only data-class threads will be compressed.&nbsp;
If you're adding a pre-sized thread, such as a comment or filename, set the
&quot;otherLen&quot; field in the data source.</p>
<p>&quot;pThreadIdx&quot; gets the thread index of the newly created
thread.&nbsp; This parameter may be set to NULL.</p>
<p>Threads will be arranged in an appropriate order that may not be the same as
the order in which NuAddThread was called.</p>
<p>If &quot;threadID&quot; indicates the thread is a disk image, then the
uncompressed length must either be a multiple of 512 bytes, or must be equal to
(recExtraType * recStorageType) in the record header.</p>
<p>On successful completion, the library takes ownership of &quot;pDataSource&quot;.&nbsp;
The structure will be freed after a NuFlush call completes successfully or all
changes are aborted.&nbsp; Until NuFlush or NuAbort completes, it is vital that
you don't free the underlying resource.&nbsp; That is, don't close the FILE*, delete the file, or
free the buffer that the data source references.&nbsp; If you don't want to keep
track of the resources used by FP and Buffer sources, you can specify &quot;fcloseFunc&quot;
or &quot;freeFunc&quot; functions to have them released automatically.&nbsp; See <a href="#sources"> the explanation of
NuDataSource</a> for details.</p>
<h4>NuError <u>NuAddFile</u>(NuArchive* pArchive, const UNICHAR* pathnameUNI, const
NuFileDetails* pFileDetails, short fromRsrcFork, NuRecordIdx* pRecordIdx)</h4>
<p>Add a file to the archive.&nbsp; This is a combination of NuAddRecord and
NuAddThread, but goes a little beyond that.&nbsp; If you add a file whose
pFileDetails-&gt;threadID indicates a data fork, and another file whose
pFileDetails-&gt;threadID indicates a resource fork, and both files have the
same pFileDetails-&gt;storageName, then the two files will be combined into a
single record.&nbsp;</p>
<p>&quot;pathnameUNI&quot; is how to open the file.&nbsp; It does not have any
bearing on the filename stored in the archive.&nbsp; Because all write
operations are deferred, NufxLib will not open or even test the existence of the
file before NuFlush is called.</p>
<p>&quot;pFileDetails&quot; describes the file types, dates, and access flags
associated with the file, as well as the filename that will be stored in the
archive (&quot;storageName&quot;).&nbsp; If two forks are placed in the same
record, whichever was added first will determine the record's characteristics.</p>
<p>&quot;fromRsrcFork&quot; should be set if NufxLib should get the data out of
the &quot;pathname&quot; file's resource fork.&nbsp; If the underlying
filesystem doesn't support resource forks, then the argument has no
effect.&nbsp; It does not have any impact on whether the data is stored as a
data fork thread or resource fork thread -- that is decided by the &quot;threadID&quot;
field of &quot;pFileDetails&quot;.</p>
<p>&quot;pRecordIdx&quot; gets the record index of the new (or existing)
record.&nbsp; This argument may be NULL.</p>
<p>The &quot;pFileDetails-&gt;storageName&quot; may not start with the filename separator
argument, i.e. &quot;/tmp/foo&quot; is illegal but &quot;tmp/foo&quot; is okay.</p>
<p>If &quot;pFileDetails-&gt;threadID&quot; indicates the thread is a disk
image, then the uncompressed length must either be a multiple of 512 bytes, or
must be equal to recExtraType * recStorageType.</p>
<p>On systems with forked files, such as GS/OS and Mac OS, it will be necessary
to call NuAddFile twice on forked files.&nbsp; The call will automatically join
forks with identical names.</p>
<p>Depending on the values of kNuValueAllowDuplicates and kNuValueHandleExisting,
this may replace an existing record in the archive.&nbsp; See <a href="#replace-existing">Replacing
Existing Records and Files</a> for details.</p>
<p>Adding a directory will not cause NufxLib to recursively descend through the
directory hierarchy.&nbsp; That's the application's job.&nbsp; Requests to add
directories are currently ignored.&nbsp; [A future release may add a
&quot;create directory&quot; control thread, so we can store empty directories.]</p>
<h4>NuError <u>NuRename</u>(NuArchive* pArchive, NuRecordIdx recordIdx, const
char* pathnameMOR, char fssep)</h4>
<p>Rename an existing record.&nbsp; Pass in the index of the record to update,
the new name, and the filename separator character.&nbsp; Setting the name to an
empty string is not permitted.</p>
<p>This call will do one of three things to the archive.&nbsp; If a filename
thread is present in the record, and it has enough room to hold the new
filename, then the existing thread will be updated.&nbsp; If a filename thread
is present, but doesn't have enough space to hold the new name, then the
existing thread will be deleted and a new filename thread will be added.&nbsp;
Finally, if no filename thread is present, a new one will be added, and the
filename in the record header (if one was set) will be dropped.</p>
<p>NufxLib does not currently test for the existence of records with an
identical name.&nbsp; This is probably a bug (ought to obey the
kNuValueAllowDuplicates setting).</p>
<h4>NuError <u>NuSetRecordAttr</u>(NuArchive* pArchive, NuRecordIdx recordIdx,
const NuRecordAttr* pRecordAttr)</h4>
<p>Set a record's attributes.&nbsp; The fields in the NuRecordAttr struct
replace the fields in the record.&nbsp; This can be used to change filetypes,
modification dates, access flags, and the file_sys_id field.</p>
<p>The changes become visible to NuContents calls only after NuFlush is called.</p>
<p>You can fill in values in the NuRecordAttr from a NuRecord struct with the NuRecordCopyAttr
call.</p>
<h4>NuError <u>NuUpdatePresizedThread</u>(NuArchive* pArchive, NuThreadIdx
threadIdx, NuDataSource* pDataSource, long* pMaxLen)</h4>
<p>Update the contents of a pre-sized thread.&nbsp; This can only be used on
filename and comment threads.&nbsp; Attempting to use it on other threads
results in a kNuErrNotPreSized return value.</p>
<p>&quot;threadIdx&quot; is the index of the thread to update, and &quot;pDataSource&quot;
is where the data comes from.&nbsp; The &quot;otherLen&quot; field in &quot;pDataSource&quot;
is ignored, because this call cannot be used to resize an existing thread.&nbsp;
(The only way to do that is to delete the thread and then create a new one.)</p>
<p>&quot;pMaxLen&quot; will hold the maximum size of the thread if the call
succeeds.&nbsp; If the call fails because the existing thread is too small, kNuErrNotPreSized
is returned and &quot;pMaxLen&quot; will be valid.&nbsp; (You can also get the
size by examining the thread's thCompThreadEOF field.)</p>
<p>This cannot be used on newly-added, deleted, or updated threads.</p>
<p>On successful completion, the library takes ownership of &quot;pDataSource&quot;.&nbsp;
The structure will be freed after a NuFlush call completes successfully or all
changes are aborted.&nbsp; Until NuFlush or NuAbort completes, it is vital that
you don't free the underlying resource.&nbsp; That is, don't close the FILE*, delete the file, or
free the buffer that the data source references.&nbsp; If you don't want to keep
track of the resources used by FP and Buffer sources, you can specify &quot;fcloseFunc&quot;
or &quot;freeFunc&quot; functions to have them closed automatically.&nbsp; See <a href="#sources"> the explanation of
NuDataSource</a> for details.</p>
<h4>NuError <u>NuDelete</u>(NuArchive* pArchive)</h4>
<p>Bulk delete.&nbsp; This tries to delete every record in the archive, invoking
the SelectionFilter callback if one has been specified.</p>
<p>You cannot delete a record that is newly-added, has been modified, has
already been deleted, or has had threads added, deleted, or updated.&nbsp; Such
records will be skipped over, so your selection filter simply won't see them.</p>
<p>Because deletion is a deferred write operation, none of the records will
actually be deleted until NuFlush is called.&nbsp; If NuDelete was successful in
its attempt to delete every record, and no new records were added, the NuFlush
call will mark the archive as being brand new (this differs from v1.0, which
failed with kNuErrAllDeleted).&nbsp; As a result, if you close the empty archive
without adding anything to it, the archive file will be removed.</p>
<h4>NuError <u>NuDeleteRecord</u>(NuArchive* pArchive, NuRecordIdx recordIdx)</h4>
<p>Delete a single record, specified by record index.</p>
<p>You cannot delete a record that is newly-added, has been modified, has
already been deleted, or has had threads added, deleted, or updated.</p>
<p>The record will be removed when NuFlush is called.</p>
<h4>NuError <u>NuDeleteThread</u>(NuArchive* pArchive, NuThreadIdx threadIdx)</h4>
<p>Delete a single thread, specified by thread index.&nbsp; If you delete all of
the threads in a record, and don't add any new ones, the record will be removed.</p>
<p>You cannot delete a thread that is newly-added, deleted, or has been updated.</p>
<p>The thread will not be removed until NuFlush is
called.</p>
<hr width="50%">
<h2><a name="general"></a><u>General Interfaces</u></h2>
<h3><a name="archive-ops"></a><u>Archive Operations</u></h3>
<h4>NuError <u>NuClose</u>(NuArchive* pArchive)</h4>
<p>Closes the
archive.&nbsp; If the archive was opened read-write, any pending changes will be
flushed first.&nbsp; If the flush attempt fails, NuClose will leave the archive open and return
with an error.</p>
<p>When the archive is closed, the temp file associated with a read/write
archive will be closed and removed.</p>
<p>All data structures associated with the archive are freed.&nbsp; Attempting
to use &quot;pArchive&quot; further results in an error (or worse).</p>
<h4>NuError <u>NuAbort</u>(NuArchive* pArchive)</h4>
<p>Abort all pending changes.&nbsp; NufxLib will throw out every pending
modification request, returning to the state it was in following the most recent
Open or Flush.</p>
<p>This does not close or manipulate any files, except for those pointed to by
data sources with &quot;fcloseFunc&quot; set.&nbsp; For the most part it simply updates internal
data structures.</p>
<p>It's perfectly safe to call this if there are no pending changes.&nbsp; The
call just returns without doing anything.</p>
<h4>NuError <u>NuGetMasterHeader</u>(NuArchive* pArchive, const
NuMasterHeader** ppMasterHeader)</h4>
<p>Get a pointer to the NuFX MasterHeader block.&nbsp; One useful item here is
the number of records in the archive.</p>
<p>IMPORTANT: do not retain the pointer after calling NuFlush or NuAbort.</p>
<h4>NuError <u>NuGetExtraData</u>(NuArchive* pArchive, void** ppData)<br>
NuError <u>NuSetExtraData</u>(NuArchive* pArchive, void* pData)</h4>
<p>Store an arbitrary void* pointer in the NuArchive structure.&nbsp; This can
be useful for accessing application data within a callback without resorting to
global variables.</p>
<h4>NuError <u>NuGetValue</u>(NuArchive* pArchive, NuValueID ident,
NuValue* pValue)<br>
NuError <u>NuSetValue</u>(NuArchive* pArchive, NuValueID ident, NuValue
value)</h4>
<p>Manipulate one of NufxLib's configurable values.&nbsp; See the <a href="#tables">tables</a>
for details.</p>
<h4>NuError <u>NuGetAttr</u>(NuArchive* pArchive, NuAttrID ident, NuAttr*
pAttr)</h4>
<p>Get an archive attribute, such as whether it's wrapped in a Binary II
header.&nbsp; See the <a href="#tables">tables</a> for details.</p>
<h4>NuError <u>NuDebugDumpArchive</u>(NuArchive* pArchive)</h4>
<p>Print debugging information to stdout.&nbsp; The output contains a rather
verbose description of the archive.&nbsp; This call is only functional if the
library was built with debugging enabled.&nbsp; If the library was built without
assertions or debug messages, this call returns an error.</p>
<p>&nbsp;</p>
<h3><a name="sources"></a><u>Data Sources</u></h3>
<p>Sources and sinks provide a way for the application to add from and extract
to something other than a named file on disk.&nbsp; There are three kinds of
sources and sinks:</p>
<ol>
<li><b>File</b> objects are named files on disk.&nbsp; They are accessed by
filename.</li>
<li><b>FP</b> objects are FILE pointers.&nbsp; Pass in a pointer to any file
at any offset.</li>
<li><b>Buffer</b> objects are pointers to memory.</li>
</ol>
<p>NuDataSource objects are used in conjunction with deferred write calls.&nbsp;
They specify a location from which data is read.&nbsp; All DataSource creation calls
take the following arguments:</p>
<ul>
<li><b>threadFormat</b> - specifies the format of the source data.&nbsp;
Usually this is kNuThreadFormatUncompressed.&nbsp; If you are moving threads
directly from one archive to another, you may not want to expand and recompress them,
so you would indicate that the data source is already compressed.</li>
<li><b>otherLen</b> - set to zero except for special situations.&nbsp; For pre-sized threads, such as comments and filenames,
this defines the size of the full buffer.&nbsp; For pre-compressed data, this
holds the threadEOF (i.e. size of the data when uncompressed).</li>
<li><b>ppDataSource</b> - a pointer to the newly-allocated NuDataSource is
placed here if the call succeeds.</li>
</ul>
<p>The remaining arguments are detailed next.</p>
<h4>NuError <u>NuCreateDataSourceForFile</u>(NuThreadFormat threadFormat,
uint32_t otherLen, const UNICHAR* pathnameUNI, short isFromRsrcFork,
NuDataSource** ppDataSource)</h4>
<p>Create a data source from a file on disk.&nbsp; Because all write operations
are deferred, the file will not actually be opened until NuFlush is
called.&nbsp; This means that if the file is unreadable or doesn't exist, the
data source create call will succeed, but the eventual NuFlush call will fail.</p>
<p>The entire contents of the file will be used.&nbsp; The file is opened when
needed and closed when processing completes.</p>
<p>&quot;pathnameUNI&quot; is the name of the file to open.&nbsp; If you use the
same pathname with more than one data source, each data source will open and
close the file.</p>
<p>&quot;isFromRsrcFork&quot; determines whether the data fork or resource fork
should be opened.&nbsp; This only has meaning on systems like Mac OS and GS/OS, where the
&quot;open&quot; call determines which fork is opened.&nbsp; For other systems,
always set it to &quot;false&quot;.</p>
<h4>NuError <u>NuCreateDataSourceForFP</u>(NuThreadFormat threadFormat, uint32_t otherLen, FILE* fp, long offset, long length,
NuCallback fcloseFunc, NuDataSource** ppDataSource)</h4>
<p>Create a data source from a FILE*.&nbsp; The FILE* must be seekable, i.e. you
can't use a stream like stdin.&nbsp; Because all write operations are deferred,
any problems with the stream, such as an early EOF, will not be detected until
the NuFlush call is made.</p>
<p>&quot;fp&quot; is the stream to use.&nbsp; It will be seeked immediately
before use, so it is permissible to use the same fp in more than one data
source.&nbsp; If you are developing for a system that differentiates between
fopen(filename, &quot;r&quot;) and fopen(filename, &quot;rb&quot;), use the
latter or you may get &quot;unexpected EOF&quot; failures.</p>
<p>&quot;offset&quot; is the starting offset in the file.&nbsp; The file will be
seeked to this point right before it is used.</p>
<p>&quot;length&quot; is the number of bytes to use.</p>
<p>The &quot;fcloseFunc&quot; parameter points to a function that calls fclose()
on its argument.&nbsp; It's bad practice (especially in the Win32 DLL world) to
allocate in the app and free in the library, so this provides a way to let the
library choose when to close the file, but let the application manage its own
heap.&nbsp; If this argument is nil, the FILE* will not be closed when
processing on this data source completes.</p>
<p>IMPORTANT: if you use the same FILE* in more than one data source, do not
provide an fcloseFunc for any of them.&nbsp; Deferred write operations are not
guaranteed to happen in any particular order, so if you set fcloseFunc the library
may close the file when it is still needed.</p>
<h4>NuError <u>NuCreateDataSourceForBuffer</u>(NuThreadFormat threadFormat,
uint32_t otherLen, const uint8_t* buffer, long offset,
long length, NuCallback freeFunc, NuDataSource** ppDataSource)</h4>
<p>Create a data source from a memory buffer.&nbsp; Invalid memory references
will not be detected until NuFlush is called.</p>
<p>&quot;buffer&quot; is a pointer to the memory you want to use.&nbsp; It is
okay for &quot;buffer&quot; to be nil so long as &quot;offset&quot; and
&quot;length&quot; are zero.&nbsp; This may be useful when creating an empty
comment thread.</p>
<p>&quot;offset&quot; is the offset from &quot;buffer&quot; at which the data
starts.</p>
<p>&quot;length&quot; is the number of bytes to use.</p>
<p>The &quot;freeFunc&quot; parameter points to a function that calls
&quot;free&quot;, &quot;delete&quot;, or &quot;delete[]&quot; on its argument.&nbsp;
There's no way for nufxlib to know exactly how the memory was allocated (malloc/new/new[]/custom),
so the application needs to supply a function to clean it up.&nbsp; If this
argument is nil, the buffer will not be freed when processing on this data
source completes..&nbsp; (Side note: the &quot;offset&quot; parameter exists so that
you can use part of a buffer and then let the library free the whole thing afterward.)</p>
<p>IMPORTANT: if you use the same memory buffer in more than one data source, do
not provide a freeFunc for any of them.&nbsp; Deferred write operations are not
guaranteed to happen in any particular order.</p>
<h4>NuError <u>NuFreeDataSource</u>(NuDataSource* pDataSource)</h4>
<p>Free a data source.&nbsp; You should only do this if the data source was not
used in a successful deferred write call.</p>
<p>If &quot;fcloseFunc&quot; or &quot;freeFunc&quot; is set in the data source, the appropriate action will
be taken.&nbsp; (NufxLib may actually make copies of DataSource objects with
ref-counting, so freeing your object may not cause an immediate fclose or free.)</p>
<h4>void <u>NuDataSourceSetRawCrc</u>(NuDataSource* pDataSource)</h4>
<p>When the data source contains already-compressed&nbsp;data, there's no way
for NufxLib to compute the CRC of the uncompressed data without expanding
it.&nbsp; Version 3 records require a data CRC in the thread header.&nbsp; This provides a way for the application to specify what value should
be in the &quot;thThreadCrc&quot; field.</p>
<p>&nbsp;</p>
<h3><a name="sinks"></a><u>Data Sinks</u></h3>
<p>NuDataSink calls are used with the thread extraction function.&nbsp; They
allow the application to specify where data is to be written to.&nbsp; All
DataSink
creation calls take the following arguments:</p>
<ul>
<li><b>doExpand</b> - should compressed data be expanded?&nbsp; You
always want to set this to &quot;true&quot;, unless you're moving compressed
data directly from one archive to another.</li>
<li><b>convertEOL</b> - determines whether the automatic EOL conversion
routines are active when expanding the thread.&nbsp; Possible values are
kNuConvertOff (never convert), kNuConvertOn (always convert), and
kNuConvertAuto (convert threads that look like ASCII text).&nbsp; This value
overrides the kNuValueConvertExtractedEOL setting.&nbsp; The EOL for
the current system can be set with the kNuValueEOL value.&nbsp; (This is
ignored if &quot;doExpand&quot; is set to &quot;false&quot;.)</li>
<li><b>ppDataSink</b> - a pointer to a newly-allocated NuDataSink is placed
here</li>
</ul>
<p>The remaining arguments are detailed next.</p>
<h4>NuError <u>NuCreateDataSinkForFile</u>(short doExpand, NuValue convertEOL,
UNICHAR* pathnameUNI, UNICHAR fssep, NuDataSink** ppDataSink)</h4>
<p>Create a data sink for a named file on disk.&nbsp; The file will be opened,
written to, and then closed.</p>
<p>Because of a peculiarity in NufxLib design, the OutputPathnameFilter callback
will be invoked during the extraction if one has been installed.&nbsp; Since
your application supplied the filename, it most likely won't want to change it,
but this can still be useful in the case where the file exists and needs to be
renamed.&nbsp; (This might even be useful, e.g. if your application insists on using
the record's filename directly when creating a data sink.)</p>
<p>&quot;pathnameUNI&quot; is the full pathname of the file to write to.</p>
<p>&quot;fssep&quot; is the filesystem separator used in the pathname.&nbsp;
This is necessary so NufxLib can build any missing directory components.</p>
<p>Using the same pathname in more than one data sink will likely yield
disappointing results, as subsequent extractions will overwite the earlier ones.</p>
<h4>NuError <u>NuCreateDataSinkForFP</u>(short doExpand, NuValue convertEOL,
FILE* fp, NuDataSink** ppDataSink)</h4>
<p>Create a data sink from a FILE*.&nbsp; The stream must be writeable, and must
be seeked to the desired offset before the extract call is made.</p>
<p>&quot;fp&quot; is the stream to use.</p>
<p>Using the same FILE* in more than one data sink isn't necessary: you can just
re-use the same data sink.&nbsp; The stream is never
seeked, so subsequent extractions will append to the earlier ones.</p>
<h4>NuError <u>NuCreateDataSinkForBuffer</u>(short doExpand, NuValue convertEOL,
uint8_t* buffer, uint32_t bufLen, NuDataSink** ppDataSink)</h4>
<p>Use a memory buffer as a data sink.</p>
<p>&quot;buffer&quot; is a pointer to the memory buffer.</p>
<p>&quot;bufLen&quot; is the maximum amount of data that the memory buffer can
hold.</p>
<p>You can re-use a buffer data sink on multiple extractions.&nbsp; The pointer
will be advanced, and bufLen decreased.&nbsp; Exceeding the size of the buffer
causes the extraction to fail with a buffer overrun error.&nbsp; (Thus, you can
extract more than one thread into the same buffer, but you can't extract one
thread into multiple buffers.)</p>
<h4>NuError <u>NuFreeDataSink</u>(NuDataSink* pDataSink)</h4>
<p>Free a NuDataSink.</p>
<h4>NuError <u>NuDataSinkGetOutCount</u>(NuDataSink* pDataSink, uint32_t*
pOutCount)</h4>
<p>Get the number of bytes that have been written to a data sink.&nbsp; The
result will be placed into &quot;pOutCount&quot;.&nbsp; This can come in handy
if you've extracted a number of things into a memory buffer and aren't sure
exactly how much is in there (perhaps because of EOL conversions).</p>
<p>&nbsp;</p>
<h3><a name="callbacks"></a><u>Callback Setters</u></h3>
<p>These functions allow you to set callbacks on a per-archive basis.</p>
<p>Most NufxLib calls are illegal in a callback function (NufxLib is not
reentrant for a single NuArchive).&nbsp; The only calls you are allowed to make
are NuGetExtraData, NuSetExtraData, NuGetValue, NuSetValue, and NuGetAttr.</p>
<p>The application must not keep copies of pointers passed to a callback.&nbsp;
If you want to keep the information from (say) a NuRecord*, you will need to copy the
contents of the struct to local storage.</p>
<p>If something has a &quot;const&quot; pointer, don't write to it.&nbsp; The
results of doing so are unpredictable (but most likely bad).</p>
<p>All callbacks are of type NuCallback, which is defined as:</p>
<code> NuResult (*NuCallback)(NuArchive* pArchive, void* args);</code>
<p>The &quot;set&quot; functions return the previous callback, all of which
default to NULL.&nbsp; If the &quot;pArchive&quot; argument is invalid, the
calls will fail and return kNuInvalidCallback.</p>
<p>&nbsp;
<h4>NuError <u>NuSetSelectionFilter</u>(NuArchive* pArchive, NuCallback
filterFunc)</h4>
<p>The selection filter callback is used to select records and threads
during bulk operations. The argument passed into the callback is a &quot;<code>const
NuSelectionProposal*</code>&quot;:
<pre>typedef struct NuSelectionProposal {
const NuRecord* pRecord;
const NuThread* pThread;
} NuSelectionProposal;
</pre>
<p>These are pointers to the NuFX record and thread that we are about to
act upon. During an extract operation, "pThread" will point at the thread
we are about to extract. During a delete operation, "pThread" will point
at the first thread in the record we are about to delete.
<p>Valid return values from a selection filter:
<ul>
<li><b>kNuOK</b> - accept this item.
<li><b>kNuSkip</b> - skip this item.
<li><b>kNuAbort</b> - abort current operation.
</ul>
<p>If no selection filter is specified, then all records will be selected.
<p>&nbsp;
<h4>NuError <u>NuSetOutputPathnameFilter</u>(NuArchive* pArchive, NuCallback
filterFunc)</h4>
<p>When extracting files, this callback allows you to change the name of
the file that will be opened on disk. It will be called once for every
thread we extract. The argument to the callback is a &quot;<code>NuPathnameProposal*</code>&quot;:
<pre>typedef struct NuPathnameProposal {
const UNICHAR* pathnameUNI;
UNICHAR filenameSeparator;
const NuRecord* pRecord;
const NuThread* pThread;
const UNICHAR* newPathnameUNI;
UNICHAR newFilenameSeparator;
NuDataSink* newDataSink;
} NuPathnameProposal;
</pre>
<p>The fields are:
<ul>
<li><b>pathnameUNI</b> - full pathname we're proposing to use.
<li><b>filenameSeparator</b> - the character used to separate pathname
components, e.g. '/', '\', or ':'. If the separator isn't recognized
by the operating system, the application will need to change it.
<li><b>pRecord</b> - pointer to the NuFX record being extracted.
<li><b>pThread</b> - pointer to the NuFX thread being extracted.
<li><b>newPathname</b> - if you want to change the pathname, set this to
a non-NULL value.
<li><b>newFilenameSeparator</b> - if you want to change the filename
separator char, set this to a nonzero value.
<li><b>newDataSink</b> - to extract the file to something other than a
filename, create a data sink and add the pointer here.
</ul>
<p>If a record contains a data fork and a resource fork,
your filter will be called twice with the same pathname. (You can
examine pThread to see what kind of fork is being extracted.) If the
OS requires that extended files be initially created as such, then the
file will always be created as "extended" if the record indicates that
a resource fork is present.
<p>This mechanism can be used to implement a "rename file being extracted"
feature. If an error handler is defined, and it returns kNuRename when
NufxLib tries to overwrite an existing file, then the
pathname filter will be invoked again.
<p>Valid return values from the output pathname filter:
<ul>
<li><b>kNuOK</b> - accept this item.
<li><b>kNuSkip</b> - skip this item.
<li><b>kNuAbort</b> - abort the current operation.
</ul>
<p>If no OutputPathnameFilter is set, the files will be opened with the
names that appear in the archive.
<p>&nbsp;
<h4>NuError <u>NuSetProgressUpdater</u>(NuArchive* pArchive, NuCallback
updateFunc)</h4>
<p>During add, extract, and test operations, NufxLib will send progress
update messages via the ProgressUpdater callback. The argument to the
callback is a &quot;<code>const NuProgressData*</code>&quot;:
<pre>typedef struct NuProgressData {
NuOperation operation;
NuProgressState state;
short percentComplete;
const UNICHAR* origPathnameUNI;
const UNICHAR* pathnameUNI;
const UNICHAR* filenameUNI;
const NuRecord* pRecord;
uint32_t uncompressedLength;
uint32_t uncompressedProgress;
struct {
NuThreadFormat threadFormat;
} compress;
struct {
uint32_t totalCompressedLength;
uint32_t totalUncompressedLength;
const NuThread* pThread;
NuValue convertEOL;
} expand;
} NuProgressData;
</pre>
<ul>
<li><b>operation</b> - the general class of operation are we performing.
<li><b>state</b> - what state are we in.
<li><b>percentComplete</b> - how far along are we, from 0 to 100.
<li><b>origPathmameUNI</b> - original pathname. When compressing, this is
the pathname for the file on disk; when expanding, this is the filename
as it appears in the archive.
<li><b>pathnameUNI</b> - the full pathname after the pathname filter (if
any) has modified it.
<li><b>filenameUNI</b> - "pathname" with everything up to the last fssep
removed.
<li><b>pRecord</b> - pointer to the record we're compressing to or
expanding from.
<li><b>uncompressedLength</b> - size of uncompressed data.
<li><b>uncompressedProgresss</b> - #of uncompressed bytes we've read
or written so far.
<li><b>compress.threadFormat</b> - type of compressing being applied.
<li><b>expand.totalCompressedLength</b> - sum of compressed lengths of
all threads in this record.
<li><b>expand.totalUncompressedLength</b> - sum of uncompressed lengths
of all threads in this record.
<li><b>expand.pThread</b> - pointer to thread we're extracting.
<li><b>expand.convertEOL</b> - EOL conversion mode used on this thread.
If it's "kConvertAuto", the first progress update after some data has
been processed will have either kConvertOn or kConvertOff.
</ul>
<p>The possible values for a NuOperation value are:
<ul>
<li><b>kNuOpUnknown</b> - unknown operation (the application should
never see this).
<li><b>kNuOpAdd</b> - files are being added to an archive.
<li><b>kNuOpExtract</b> - records or threads are being extracted from
an archive.
<li><b>kNuOpTest</b> - records are being tested.
<li><b>kNuOpDelete</b> - records or threads are being deleted.
<li><b>kNuOpContents</b> - the archive contents are being examined.
</ul>
<p>Deleting files and listing contents don't cause the progress update
callback to be called, so you'll never see "kNuOpDelete" or "kNuOpContents"
in a progress handler.
The possible values for a NuProgressState value are:
<ul>
<li><b>kNuProgressPreparing</b> - not started yet
<li><b>kNuProgressOpening</b> - opening files
<li><b>kNuProgressCompressing</b> - compressing data
<li><b>kNuProgressStoring</b> - storing data (without compression)
<li><b>kNuProgressExpanding</b> - expanding data
<li><b>kNuProgressCopying</b> - copying uncompressed data (in or out)
<li><b>kNuProgressDone</b> - all done, success
<li><b>kNuProgressSkipped</b> - all done, we skipped this one
<li><b>kNuProgressAborted</b> - all done, user cancelled the operation
<li><b>kNuProgressFailed</b> - all done, failure
</ul>
<p>Some values (say, kNuProgressCompressing) are only appropriate for
certain operations (kNuOpAdd).
<p>Valid return values from a progress updater are:
<ul>
<li><b>kNuOK</b> - all is well.
<li><b>kNuAbort</b> - abort operation.
</ul>
<p>If no ProgressUpdater is defined, no progress update information will
be sent.
<p>&nbsp;</p>
<h4>NuError <u>NuSetErrorHandler</u>(NuArchive* pArchive, NuCallback errorFunc)</h4>
<p>The ErrorHandler callback deals with all exceptional conditions that
arise. The callback may define hard-coded policy or query the user for
directions. The argument to the callback is a &quot;<code>const NuErrorStatus*</code>&quot;:
<pre>typedef struct NuErrorStatus {
NuOperation operation;
NuError err;
int sysErr;
const UNICHAR* message;
const NuRecord* pRecord;
const UNICHAR* pathnameUNI;
const void* origPathname;
UNICHAR filenameSeparator;
char canAbort;
char canRetry;
char canIgnore;
char canSkip;
char canRename;
char canOverwrite;
} NuErrorStatus;
</pre>
<ul>
<li><b>operation</b> - the general class of operation we are performing.
<li><b>err</b> - NufxLib error code.
<li><b>sysErr</b> - system error code, if applicable.
<li><b>message</b> - (optional) message to user.
<li><b>pRecord</b> - (optional) relevant record.
<li><b>pathnameUNI</b> - (optional) name of file or record involved.
<li><b>origPathname</b> - (optional) when adding, name of original file.&nbsp;
Note this is a void*, so it can be an object reference rather than a string.<li><b>filenameSeparator</b> - (optional) fssep in use at time.
<li><b>canAbort</b> - callback may return kNuAbort.
<li><b>canRetry</b> - callback may return kNuRetry.
<li><b>canIgnore</b> - callback may return kNuIgnore.
<li><b>canSkip</b> - callback may return kNuSkip.
<li><b>canRename</b> - callback may return kNuRename.
<li><b>canOverwrite</b> - callback may return kNuOverwrite.
</ul>
<p>Some situations that may arise:
<dl>
<dt>operation == kNuOpExtract
<dl>
<dt>err == kNuErrFileExists
<dd>We're extracting a file to the same pathname as an existing
file, and our overwrite policy is set to "maybe".
<dt>err == kNuErrNotNewer
<dd>We're extracting a file to the same pathname as an existing
file, and we're only allowed to overwrite older files.
<dt>err == kNuErrDuplicateNotFound
<dd>We're extracting a file in the "must overwrite" mode, but
the file doesn't exist.
</dl>
<dt>operation == kNuOpAdd
<dl>
<dt>err == kNuErrRecordExists
<dd>We're adding a file whose "storage name" matches a file
already in the archive, and our overwrite policy is set to "maybe".
<dt>err == kNuErrFileNotFound
<dd>We tried to add a file, but when we went to open it the
file didn't exist.
</dl>
<dt>operation == kNuOpTest
<dl>
<dt>err == kNuErrBadMHCRC
<dd>The master header CRC was bad.
<dt>err == kNuErrBadRHCRC
<dd>A record header CRC was bad.
<dt>err == kNuErrBadThreadCRC
<dd>A thread header CRC was bad.
<dt>err == kNuErrBadDataCRC
<dd>A thread in the data (e.g. LZW/1 CRC) was bad.
</dl>
</dl>
<p>The valid return values are defined by the NuErrorStatus structure.
<p>If no ErrorHandler is defined, an appropriate default action (usually
kNuAbort) is taken.
<p>&nbsp;</p>
<h4>NuError <u>NuSetErrorMessageHandler</u>(NuArchive* pArchive, NuCallback messageFunc)<br>
NuError <u>NuSetGlobalErrorMessageHandler</u>(NuCallback messageFunc)</h4>
<p>Specify a callback to receive text error messages.&nbsp; These are typically
an error message followed by an explanation of the error code that the library
is about to return.&nbsp; The callback takes an argument of type &quot;<code>const
NuErrorMessage*</code>&quot;, which is defined as:</p>
<pre>typedef struct NuErrorMessage {
const char* message;
NuError err;
short isDebug;
const char* file;
int line;
const char* function;
} NuErrorMessage;
</pre>
<ul>
<li><b>message</b> - the message to display.
<li><b>err</b> - relevant error code, if any.
<li><b>isDebug</b> - set for debug-only messages. You should only get
these messages if the library is built with DEBUG_MSGS enabled.
<li><b>file</b> - (if library built with DEBUG_MSGS) source file where
message originated.
<li><b>line</b> - (if library built with DEBUG_MSGS) source line number.
<li><b>function</b> - (if library built with DEBUG_MSGS, and compiler
supports __FUNCTION__) name of function where message originated.
</ul>
<p>The return value is ignored.
<p>Some error messages aren't associated with an archive, generally because they
occur when an archive is being opened.&nbsp; Since there's no way to associate
them with a single archive, the handler must be global to the entire
library.&nbsp; The second form of this call allows you to specify where global
error messages should be sent.&nbsp; The arguments to the callback are
identical, but &quot;pArchive&quot; will be nil.</p>
<p>If no callback is specified, the messages are sent to stderr.&nbsp; If your
application doesn't have a stderr (perhaps it's a GUI application), be sure to
set both the ErrorMessag and GlobalErrorMessage handlers.</p>
<hr width="50%">
<h2><a name="helper"></a><u>Helper Functions</u></h2>
<p>Some of these are macros, some are functions.&nbsp; None require that an
archive be open.</p>
<h4>NuError <u>NuGetVersion</u>(int32_t* pMajorVersion, int32_t* pMinorVersion,
int32_t* pBugVersion, const
char** ppBuildDate, const char** ppBuildFlags)</h4>
<p>Get some information about NufxLib's version.&nbsp; This sets the major and
minor version numbers, as well as setting strings with the build date and some
build flags.</p>
<p>Any or all of the arguments may be NULL, for values you aren't interested in.</p>
<p>The format of &quot;ppBuildDate&quot; is not defined [though it probably
should be].</p>
<p>The format of &quot;ppBuildFlags&quot; is a string of compiler flags
separated by white space (spaces or tabs).&nbsp; It is expected to represent an
&quot;interesting subset&quot; of the flags sent to the compiler, such as the
level of optimization used.</p>
<h4>const char* <u>NuStrError</u>(NuError err)</h4>
<p>Return a pointer to a string describing a NufxLib error.&nbsp; NufxLib errors
are &quot;err&quot; values less than zero.&nbsp; &quot;err&quot; values greater
than zero are system errors that can be processed with strerror() or perror(),
and an &quot;err&quot; value of zero indicates success.</p>
<h4>NuError <u>NuTestFeature</u>(NuFeature feature)</h4>
<p>Test for support of an optional feature.&nbsp; See the <a href="#tables">tables</a>
for a list.&nbsp; Returns kNuErrNone on success, kNuErrUnsupFeature if the
feature is known but not supported, or kNuErrUnknownFeature if the feature is
not recognized at all (probably because the version of NufxLib you're linked
with is older than what you compiled against).</p>
<h4>uint32_t <u>NuMakeThreadID</u>(unsigned short class, unsigned short
kind)</h4>
<p>Construct a NuThreadID, given a thread class and thread kind.</p>
<h4>uint32_t <u>NuGetThreadID</u>(const NuThread* pThread)</h4>
<p>Construct a NuThreadID, using the thread class and thread kind defined in a
NuThread.</p>
<h4>uint16_t <u>NuThreadIDGetClass</u>(NuThreadID threadID)</h4>
<p>Pull the thread class out of a NuThreadID.</p>
<h4>uint16_t <u>NuThreadIDGetKind</u>(NuThreadID threadID)</h4>
<p>Pull the thread kind out of a NuThreadID.</p>
<h4>char <u>NuGetSepFromSysInfo</u>(unsigned short sysInfo)</h4>
<p>Pull the filename separator character out of the file_sys_info word.</p>
<h4>uint16_t <u>NuSetSepInSysInfo</u>(unsigned short sysInfo, char newSep)</h4>
<p>Put the filename separator character into a file_sys_info word.&nbsp; Returns
the new value.</p>
<h4>uint32_t <u>NuRecordGetNumThreads</u>(const NuRecord* pRecord)</h4>
<p>Return the number of threads in a record.</p>
<h4>const NuThread* <u>NuGetThread</u>(const NuRecord* pRecord, int idx)</h4>
<p>Get the idx-th thread from pRecord.&nbsp; If idx is less than zero or past
the end of the thread array, nil is returned.</p>
<h4>void <u>NuRecordCopyAttr</u>(NuRecordAttr* pRecordAttr, const NuRecord*
pRecord)</h4>
<p>Copy data from &quot;pRecord&quot; into &quot;pRecordAttr&quot;.&nbsp; Only
the fields that exist in a NuRecordAttr are copied.&nbsp; This can be useful in
conjunction with the SetRecordAttr call.</p>
<h4>NuError <u>NuRecordCopyThreads</u>(const NuRecord* pRecord, NuThread**
ppThreads)</h4>
<p>Copy the thread array out of a record.&nbsp; This is useful if you want to
keep your own copy of a thread array.</p>
<h4>short <u>NuIsPresizedThread</u>(NuThreadID threadID)</h4>
<p>Returns &quot;true&quot; if the threadID is considered pre-sized by
NufxLib.&nbsp; Right now, only filenames and comments are given this treatment.</p>
<h4>size_t <span class="auto-style1">NuConvertMORToUNI</span>(const char*
stringMOR, UNICHAR* bufUNI, size_t bufSize)</h4>
<p>Convert Mac OS Roman to Unicode (UTF-8 or UTF-16).&nbsp; Returns the number
of bytes required to hold the converted string.&nbsp; &quot;bufUNI&quot; may be NULL.&nbsp;
[Not implemented for Win32.]</p>
<h4>size_t <span class="auto-style1">NuConvertUNIToMOR</span>(const UNICHAR*
stringUNI, char* bufMOR, size_t bufSize)</h4>
<p>Convert Unicode to Mac OS Roman.&nbsp; Returns the number of bytes required
to hold the converted string.&nbsp; &quot;bufMOR&quot; may be NULL.&nbsp; [Not implemented
for Win32.]</p>
<p>&nbsp;</p>
<hr>
<h2><a name="tables"></a><u>Tables</u></h2>
<h3><u>Configurable Values</u> (NuValue)</h3>
<table border="1" width="100%">
<tr>
<td width="32%">kNuValueIgnoreCRC</td>
<td width="68%">Boolean (false).&nbsp; Don't verify header or data CRCs.&nbsp;
This can provide a minor speed improvement, but allows certain kinds of
errors to go undetected.</td>
</tr>
<tr>
<td width="32%">kNuValueDataCompression</td>
<td width="68%">Enum (kNuCompressLZW2).&nbsp; Threads that can be compressed
(i.e. data-class threads) will be compressed with the specified
compression.&nbsp; Possible values are:
<ul>
<li>kNuCompressNone (no compression)</li>
<li>kNuCompressSQ (SQueeze)</li>
<li>kNuCompressLZW1 (ShrinkIt's dynamic LZW/1)</li>
<li>kNuCompressLZW2 (ShrinkIt's dynamic LZW/2)</li>
<li>kNuCompressLZC12 (12-bit LZW from &quot;compress&quot;)</li>
<li>kNuCompressLZC16 (16-bit LZW from &quot;compress&quot;)</li>
<li>kNuCompressDeflate [requires zlib]</li>
</ul>
</td>
</tr>
<tr>
<td width="32%">kNuValueDiscardWrapper</td>
<td width="68%">Boolean (false).&nbsp; If changes are made to the archive
that cause a new copy to be reconstructed in the temp file, then when this
is set to &quot;true&quot; any BXY, BSE, or SEA wrapper will be stripped
off.&nbsp; This also causes any &quot;junk&quot; at the start of the file
to be removed.</td>
</tr>
<tr>
<td width="32%">kNuValueEOL</td>
<td width="68%">Enum (system-dependent).&nbsp; End-of-line marker
appropriate for the current system.&nbsp; If EOL conversion is enabled,
extracted files will be converted to this EOL value.&nbsp; Valid values
are:
<ul>
<li>kNuEOLCR (carriage return, for ProDOS, GS/OS, Mac OS)</li>
<li>kNuEOLLF (line feed, for UNIX)</li>
<li>kNuEOLCRLF (CR+LF, for MS-DOS and Win32)</li>
</ul>
</td>
</tr>
<tr>
<td width="32%">kNuValueConvertExtractedEOL</td>
<td width="68%">Enum (kNuConvertOff).&nbsp; This determines whether
&quot;bulk&quot; extractions do EOL conversions.&nbsp; Possible values:
<ul>
<li>kNuConvertOff (don't try to convert)</li>
<li>kNuConvertOn (always convert)</li>
<li>kNuConvertAuto (convert if the input appears to be a text file)</li>
</ul>
</td>
</tr>
<tr>
<td width="32%">kNuValueOnlyUpdateOlder</td>
<td width="68%">Boolean (false).&nbsp; If set, only overwrite existing
records and files if the item being added or extracted is newer than the
one being replaced.&nbsp; Useful for an &quot;update&quot; or
&quot;freshen&quot; option.&nbsp; The date used for comparison is the
modification date.</td>
</tr>
<tr>
<td width="32%">kNuValueAllowDuplicates</td>
<td width="68%">Boolean (false).&nbsp; If set to &quot;true&quot;, duplicate
records are allowed in the archive.&nbsp; If &quot;false&quot;, the
collision will be handled according to the kNuValueHandleExisting
setting.&nbsp; Filename comparisons are case-insensitive.</td>
</tr>
<tr>
<td width="32%">kNuValueHandleExisting</td>
<td width="68%">Enum (kNuMaybeOverwrite).&nbsp; This determines how
duplicate filename collisions are handled.&nbsp; Valid values:
<ul>
<li>kNuMaybeOverwrite (the ErrorHandler callback is invoked)</li>
<li>kNuNeverOverwrite (the file being added or extracted is skipped)</li>
<li>kNuAlwaysOverwrite (the existing file or record is deleted)</li>
<li>kNuMustOverwrite (fails if the file or record doesn't exist, useful
for a &quot;freshen&quot; option)</li>
</ul>
<p>The case sensitivity when extracting is determined by the underlying
filesystem.
</td>
</tr>
<tr>
<td width="32%">kNuValueModifyOrig</td>
<td width="68%">Boolean (false, unless the archive was just created by
NufxLib).&nbsp; If this is &quot;true&quot;, then an effort will be made
to handle all updates in the original archive, rather than reconstructing
the entire archive in a temp file.&nbsp; Updates to pre-sized threads,
changes to record attributes, and additions of new files can all be made
to the original archive.&nbsp; There is some risk of corruption if the
flush fails, so use this with caution.</td>
</tr>
<tr>
<td width="32%">kNuValueMimicSHK</td>
<td width="68%">Boolean (false).&nbsp; If set, attempt to mimic the behavior
of ShrinkIt as closely as possible.&nbsp; See the <a href="#shrinkit">ShrinkIt
Compatibility Mode</a> section.</td>
</tr>
<tr>
<td width="32%">kNuValueMaskDataless</td>
<td width="68%">Boolean (false).&nbsp; If set to &quot;true&quot;, records
without data threads have &quot;fake&quot; threads created for them, so
that they appear as they would had they been created correctly.</td>
</tr>
<tr>
<td width="32%">kNuValueStripHighASCII</td>
<td width="68%">Boolean (false).&nbsp; If set to &quot;true&quot;, files
filled with high-ASCII characters will be stripped if and only if an EOL
conversion is performed.&nbsp;</td>
</tr>
<tr>
<td width="32%">kNuValueJunkSkipMax</td>
<td width="68%">Integer (1024).&nbsp; If the archive file doesn't start with
a recognized sequence, NufxLib will assume that some junk has been added
to the start of the file and will scan forward at most this many bytes in
an attempt to locate the real archive start.</td>
</tr>
<tr>
<td width="32%">kNuValueIgnoreLZW2Len</td>
<td width="68%">Boolean (false).&nbsp; If set to &quot;true&quot;, the
length value embedded in LZW2 compressed chunks is ignored.&nbsp; This is
useful for archives created with a specific broken application.&nbsp;
(This is deprecated -- use HandleBadMac instead.)</td>
</tr>
<tr>
<td width="32%">kNuValueHandleBadMac</td>
<td width="68%">Boolean (false).&nbsp; Recognize and handle &quot;bad
Mac&quot; archives, which have a bad value ('?') for the filename
separator character, and write an LZW/2 length value in big-endian order.</td>
</tr>
</table>
<p>&nbsp;</p>
<h3><u>Archive Attributes</u> (NuAttr)</h3>
<table border="1" width="100%">
<tr>
<td width="32%">kNuAttrArchiveType</td>
<td width="68%">Returns one of the following:
<ul>
<li>kNuArchiveNuFX (NuFX archive, e.g. &quot;.SHK&quot; or
&quot;.SDK&quot;)</li>
<li>kNuArchiveBinaryII (Binary II, e.g. &quot;.BNY&quot; or &quot;.BQY&quot;)
[not supported]</li>
<li>kNuArchiveNuFXInBNY (NuFX inside Binary II, e.g. &quot;.BXY&quot;)</li>
<li>kNuArchiveNuFXSelfEx (self-extracting GSHK archive, e.g.
&quot;.SEA&quot;)</li>
<li>kNuArchiveNuFXSelfExInBNY (self-ex inside Binary II, e.g. &quot;.BSE&quot;)</li>
</ul>
</td>
</tr>
<tr>
<td width="32%">kNuAttrNumRecords</td>
<td width="68%">Returns the number of records in the archive.&nbsp; This
value does not reflect unflushed changes.
</td>
</tr>
<tr>
<td width="32%">kNuAttrHeaderOffset</td>
<td width="68%">Returns the offset of the NuFX header from the start of the
file.&nbsp; This will be nonzero for archives with a Binary II or
self-extracting wrapper.
</td>
</tr>
<tr>
<td width="32%">kNuAttrJunkOffset</td>
<td width="68%">Returns the amount of junk found at the start of the
file.&nbsp; A nonzero value here indicates that junk was found.
</td>
</tr>
</table>
<h3>&nbsp;</h3>
<h3><u>Feature Tests</u> (NuFeature)</h3>
<table border="1" width="100%">
<tr>
<td width="32%">kNuFeatureCompressSQ</td>
<td width="68%">Test for support of SQueeze compression
</td>
</tr>
<tr>
<td width="32%">kNuFeatureCompressLZW</td>
<td width="68%">Test for support of ShrinkIt LZW/1 and LZW/2 compression
</td>
</tr>
<tr>
<td width="32%">kNuFeatureCompressLZC</td>
<td width="68%">Test for support of 12- and 16-bit LZC
</td>
</tr>
<tr>
<td width="32%">kNuFeatureCompressDeflate</td>
<td width="68%">Test for support of zlib &quot;deflate&quot;
</td>
</tr>
<tr>
<td width="32%">kNuFeatureCompressBzip2</td>
<td width="68%">Test for support of libbz2 &quot;bzip2&quot;
</td>
</tr>
</table>
<p>&nbsp;</p>
<hr>
<h2><a name="commentary"></a><u>Additional Commentary</u></h2>
<h3><a name="replace-existing"></a><u>Replacing Existing Records and Files</u></h3>
<p>
When using NuAddFile or NuAddRecord, there are three flags that affect
what happens when an existing record has the same name:
<dl>
<dt><b>AllowDuplicates</b> (default false)</dt>
<dd>if set, adding a record with the same name as an
existing record is allowed.
<dt><b>OnlyUpdateOlder</b> (default false)</dt>
<dd>if set, we refuse to replace an existing record
unless its modification date is older.
<dt><b>HandleExisting</b> (default &quot;maybe overwrite&quot;)</dt>
<dd>can be set to "maybe overwrite" (prompts the user),
"never overwrite" (returns an error), "always overwrite" (overwrites
the existing record), and "must overwrite" (causes an error if the
record *doesn't* exist). The "maybe overwrite" value is treated as
"never overwrite" if an error-handling callback isn't defined.
</dl>
It's important to understand how these interact with each other, and what
they mean to both existing records and newly-added (pre-flush) records.
Two of them also have an effect when extracting files.
<p>
The AllowDuplicates flag determines whether or not we
think duplicate records are at all interesting. If an application sets it to
true, the floodgates are opened, and the two other flags are ignored.
<p>
The OnlyUpdateOlder flag is considered next. If it's
set to true, and an existing, identically named file in the archive appears
to be the same age or newer than the file being added, the record creation
attempt fails with an error (kNuErrNotNewer).
<p>
The HandleExisting flag comes into play if
we get past the first two. If a matching entry is found in the archive,
NufxLib either deletes it and allows the add, prompts the user for
instructions, or rejects it with an error. NuAddFile and NuAddRecord will
return with kNuErrRecordExists if they can't replace an existing record.
If "must overwrite" is set, and a matching record does not exist, then
kNuErrDuplicateNotFound is returned.
<p>
Both AddFile and AddRecord check for duplicates among existing and newly
added files. You aren't allowed to delete items that were just added, so
HandleExisting flag is ignored for files you have marked for addition
but haven't yet flushed.
<p>
AddFile has an additional behavior that takes precedence over all of
the flags: it will try to match up the individual forks of a file.
If it finds a file in the newly-added list with the
same name and a compatible data thread, the new file will be added to
the existing record. (A "compatible" data thread is the other half of a
forked file, e.g. the application added the data fork, and is now adding
the resource fork from the same file.) If the record was found but is not
compatible, the AllowDuplicates behavior is used to decide if another
"new" record with the same name should be added, or if an error should
be returned.
<p>
If this sort of treatment is undesirable, i.e. you <i>want</i> a data fork
and a resource fork with the same filename to be stored as two separate
records, then you should call AddRecord and AddThread. AddFile is meant
as a convenience for common operations.
<p>
It is possible for NuAddFile and NuAddRecord to partially complete. If
a record exists and is deleted, but the call later fails for some other
reason, the record will still be deleted.<p>Searching for existing records can
take time on a large archive.&nbsp; Disabling AllowDuplicates will allow NufxLib
to avoid having to search through the lists of records to find matches.
<p>&nbsp;<p>
When extracting files from an archive, the "OnlyUpdateOlder" and
"HandleExisting" flags are applied to the files on disk. This is done
much like the above.
<p>
To implement NuLib2's "update" feature, &quot;OnlyUpdateOlder&quot; needs to be
set to true. To implement the "freshen" feature, &quot;OnlyUpdateOlder&quot;
is set to true and &quot;HandleExisting&quot; is set to "must overwrite".
<p>
When extractions are done in bulk, the kNuErrDuplicateNotFound and
kNuErrNotNewer errors are passed to the application's error handler
function. The error handler is expected to return kNuSkip after perhaps
updating the progress status message, but is allowed to abort or require
NufxLib to overwrite the file anyway. If no error handler is defined,
the file is skipped silently.
<p>&nbsp;</p>
<h3><a name="shrinkit"></a><u>ShrinkIt Compatibility Mode</u></h3>
<p>One of the goals was to be as compatible with ShrinkIt as possible.&nbsp;
ShrinkIt and GS/ShrinkIt occasionally do some strange things, so some of the
compatible behaviors are only activated when the &quot;mimic ShrinkIt&quot; flag
is set.</p>
<p>These behaviors are:</p>
<ul>
<li>When new files are added to an archive, the first record has a blank
comment added.&nbsp; The size in GSHK is configurable, but defaults to 200
bytes, so that's what NufxLib uses.&nbsp; (If you've already provided a comment
for the first new record, an additional comment won't be added.)</li>
<li>All threads compressed with LZW have an extra zero byte added to the end.</li>
<li>When deciding if a 4K chunk was compressed with LZW/2, ShrinkIt compares
the size of the post-RLE output to the size of the LZW output.&nbsp; It
doesn't take into account a two-byte header (the compressed data size) that is
prepended to the block.&nbsp; This causes it on rare occasions to choose a
compressed block that is a byte or two larger than the pre-LZW data.</li>
<li>Files smaller than 512 bytes are never compressed.</li>
<li>All archives with SEA wrappers have an extra byte added to the end.&nbsp;
(For BSE archives, where a Binary II wrapper is placed around the SEA
wrapper, I have chosen *not* to emulate GSHK's behavior in one particular
circumstance: if the SEAed archive is an exact multiple of 128 bytes, GSHK
still adds the extra byte, making the BSE file an odd size.&nbsp; Binary II
files should always be a multiple of 128 bytes.)</li>
</ul>
<p>Some GS/ShrinkIt behaviors are not fully emulated:</p>
<ul>
<li>The test on LZW/2 chunk size is inconsistent: sometimes the
comparison is &quot;LZW &lt; post-RLE&quot;, sometimes the comparison is
&quot;LZW &lt;= post-RLE&quot;.&nbsp; My guess is that ShrinkIt is failing
to clear the carry flag before a comparison.&nbsp; Whatever the case, it's
impossible to imitate correctly, so NufxLib always uses &quot;LZW &lt; post-RLE&quot;.</li>
<li>When adding a zero-byte data fork,
no data thread is created.&nbsp; The record contains nothing but a filename
thread (and perhaps a comment thread).&nbsp; This appears to be a bug.&nbsp;
Similar behavior occurs for empty forked files.</li>
</ul>
<p>Regarding the last item: a quick test with a handful of empty
files showed that GS/ShrinkIt v1.1 failed to extract the empty files it had just
archived.&nbsp; P8 ShrinkIt v3.4 gets really confused on such archives, and insists that the
first entry is a zero-byte disk archive, while the other empty files are
actually four bytes long.&nbsp; When asked to extract the files, it does
nothing.&nbsp; When adding empty files, P8 ShrinkIt v3.4 does the correct thing, and creates an empty
data thread.</p>
<p>The default NufxLib behavior is to work around the bug.&nbsp; When extracting files with a filename but no data or control threads, a zero-byte data file will be
created.&nbsp; (In NufxLib v1.0 the default was to ignore such entries unless
the &quot;mimic&quot; flag was enabled.&nbsp; This was changed in v1.1 to be
enabled at all times.&nbsp; As of v1.2, an empty resource fork is also created
if the record's storage type indicates it's an extended file.)&nbsp; If the
&quot;MaskDataless&quot; flag is enabled, fake data threads are created, and
applications won't even know there's a problem in the archive.</p>
<p>In general, with &quot;mimic ShrinkIt&quot; mode enabled, it should be
possible to extract files from a GS/ShrinkIt archive and re-add them to a new
archive, with little perceptible difference between the two.&nbsp; Of course, it's
up to the application to ensure that all threads (including comments) are
retained, file dates aren't altered, and so on.&nbsp; The only situations where
NufxLib cannot produce identical results are bugs (e.g. zero-length
data files always require more space) and option lists (which NufxLib does not currently support).</p>
<p>The bottom line: it is perfectly normal for NufxLib archives to be a few
bytes smaller than GS/ShrinkIt archives, even when &quot;mimic ShrinkIt&quot;
mode is enabled.&nbsp; (An example: my 20MB boot partition compressed to about
14MB.&nbsp; With &quot;mimic&quot; mode off, the file was 13K smaller, or about
0.1%.)</p>
<p>&nbsp;</p>
<h3><a name="compression-formats"></a><u>Compression Formats</u></h3>
<p>
Of the various compression formats that NufxLib supports, only LZW/1 and LZW/2 are
widely supported. The latest versions of ShrinkIt and II Unshrink
try to unpack SQ compression but fail. Archives that use SQ, LZC12,
and LZC16 can only be unpacked by GS/ShrinkIt, NuLib, and NufxLib v1.1 and later.
<p>The "deflate" and &quot;bzip2&quot; algorithms are not supported by anything other than NufxLib
v1.1+ and CiderPress.
They are intended to be used with archives that will never be
unpacked on an Apple II. Disk images compressed with these algorithms are especially useful with
emulators that use NufxLib.</p>
<p>Some tests with deflate and bzip2 showed that, surprisingly, deflate is
nearly always better than bzip2 for Apple II files.&nbsp; This is because
deflate appears to do slightly better on machine code and small (&lt; 32K) text
files.&nbsp; Since most Apple II files and disk images fall into these
categories, there is little advantage to using bzip2.&nbsp; Because deflate uses
less memory and is faster, and libbz2 isn't nearly as ubiquitous as libz, I've
chosen to disable bzip2 by default.</p>
<p>You can use the NuFeatureTest call to test for the presence of any of the
compression algorithms.&nbsp; This makes it possible to build a library or DLL
without LZW in it.</p>
<hr>
<h2><a name="porting"></a><u>Porting</u></h2>
<p>NufxLib v1.0 was developed under Solaris 2.5 and Red Hat Linux 6.0, and was ported
to Win32 shortly before the alpha release.&nbsp; Porting to other UNIX-like
platforms has been straightforward, with most differences contained in the
&quot;autoconf&quot; configuration system.&nbsp; For example, the BeOS/PPC port
was largely a matter of getting the compiler settings right.</p>
<p>Mac OS and GS/OS have the ability to store file types and resource forks
natively.&nbsp; Support for this is not currently part of NufxLib.&nbsp; A
data-fork-only port, akin to what is used on UNIX and Win32, should be
straightforward though.&nbsp; (In fact, Mac OS X &quot;just worked&quot;.)</p>
<p>Once upon a time a GS/OS port was imagined.&nbsp; This never happened, and
likely never will.</p>
<hr>
<h2><a name="design-notes"></a><u>Design Notes</u></h2>
<p>The decision to pass FILE* structures instead of file descriptors was
somewhat arbitrary.&nbsp; The library uses buffered I/O internally, so it was
convenient to have them passed in, rather than having an fd passed in and rely
on the existence of an fdopen() call.&nbsp; On the other hand, if an application
is built with a different version of the stdio library (in which the structure of
a FILE* are different), linking with NufxLib might not work.&nbsp; Given that NufxLib
is distributed as BSD-licensed source code, I don't see this as
being a major problem, since you can always rebuild NufxLib with the altered
stdio lib.&nbsp; (This caused me some grief under Windows, because the non-debug
multithreaded DLL version of libc apparently does something wonky with FILE* and
fwrite.&nbsp; If the Win32 DLL and Win32 app aren't linked against the same libc,
fwrite() will crash.&nbsp; Other versions of libc, e.g. debug multithreaded and
debug single-threaded, interact just fine with each other.)&nbsp; My conclusion
after fighting with Win32 is that it would have been better to pass file
descriptors or a &quot;NuFILE*&quot; with read/write/seek operations that reside
wholly within the NufxLib library.</p>
<p>The decision to pass data sources and sinks around as structures rather than as
function pointers was born of a desire to reduce complexity.&nbsp; Setting up a data
source or sink requires making a function call with a lot of arguments, but once
that's done you can forget all about it -- the code will happily close your
files and free your memory when you're done with it.&nbsp; A functional
interface would require passing in read, write, close, and seek functions, which
gives the application more flexibility but essentially requires the application
to implement its own version of the data source and sink structures.&nbsp; Since
NufxLib is intended for manipulating archives, not compressing streams of data,
the added flexibility did not justify the cost.&nbsp; (I'm becoming less certain
of that as time goes by.&nbsp; If I had it all to do again, I probably would use
the functional interface for all file accesses.)</p>
<p>It might have been useful to allow read/write/seek hooks for the archive
itself.&nbsp; The current architecture prevents you from processing an archive
that has been loaded into memory, unless you have memory-based FILE* streams in your
libc.&nbsp; This became annoying during the development of CiderPress, because I
wanted to handle archives within a wrapper, such as &quot;.shk.gz&quot;.</p>
<p>Returning a pointer to an allocated NuArchive structure worked pretty well
until I wanted to set a parameter that affected the way open works (the
junk-skipping feature).&nbsp; Creating the structure in a separate call before
the &quot;open&quot; would
have been better.</p>
<p>The biggest problem with NufxLib is that it tries to do too much. A much
larger share of the work should have been done in NuLib2. The easiest way to
see this is to look at a feature like the built-in end-of-line conversion. It's
certainly handy to have, but applications like NuLib2 and CiderPress work with
more than one type of archive, which means we either won't support EOL conversions
(e.g. NuLib2 doesn't convert when extracting from BNY) or we have to
reimplement it in the app anyway (e.g. CiderPress has a complete copy of the NufxLib
implementation). Nearly everything relating to files should have been done by
the application, with NufxLib using a file descriptor or memory buffer to
access the archive, and app-supplied read/write functions to handle compressed
data.</p>
<p>There is no real support for GS/OS option lists.&nbsp; The only place you'd
ever want to add these is on a IIgs, and I find it unlikely that NufxLib will
edge out GS/ShrinkIt as the preferred archiver.&nbsp; (Besides, I question their
value even on a IIgs.)&nbsp; NufxLib will very carefully preserve them when modifying
a record, but there's no way to add, delete, or modify them directly.</p>
<p>The use of RecordIdx and ThreadIdx, rather than record filename and thread
offset number, was chosen for a number of reasons.&nbsp; The most important was
that they are unambiguous.&nbsp; Consider that two records may have the same
filename, that one record may have two filenames, and that a record may have no
filename at all, and the need for RecordIdx becomes painfully clear.&nbsp; I
could've avoided ThreadIdx, by using a combination of RecordIdx and thread
number, but after a few additions and deletions there is a clear advantage to
having a unique identifier for every thread.&nbsp; Besides, it allowed me to
design calls like NuExtractThread so that they only had to take one identifier
as an argument, reducing the amount of stuff an application needs to keep track
of, as well as the amount of error checking that has to be done in the library.</p>
<p>Allowing threads to be copied without expanding and recompressing them is
neat, but if I 'd know how cluttered the interface would become I probably
wouldn't have supported the feature.&nbsp; The NuDataSource calls are confusing
enough as it is with the pre-sized thread stuff.</p>
<p>The &quot;bulk&quot; NuAdd interface can be cumbersome.&nbsp; When extracting
you can skip the bulk approach and handle filename conflicts yourself, but when
adding you don't have a good alternative if you're adding lots of files.&nbsp;
You would have to follow every NuAdd with a NuFlush, which has some performance
problems because (unless you configure the safety options off) NuFlush will
write all data to the temp file and rotate it.</p>
<p>I chose not to implement EOL conversions when adding files.&nbsp; It's too
painful to do this in the library.&nbsp; It would be easier for the application
to write an EOL-converted file into a temp file, then use the file add call on
that.&nbsp; (The &quot;storage name&quot; is set independently from the source
file name, so there's no problem with temp file names showing up in the
archive.)&nbsp; One approach that could be used within NufxLib would be to
&quot;pre-flight&quot; the file by doing an EOL conversion pass to determine the
final file length, then feed that length into the compression functions.&nbsp;
The &quot;NuStraw&quot; interface would do the conversion transparently to the
compression routines.</p>
<p>The compression functions might have been better written with a zlib-like
API.&nbsp; This would have made it easier to extract the code and use it in
other projects.&nbsp; The only disadvantage of doing so is that it adds a little
extra buffer copying overhead.</p>
<p>Some attention should have been paid to internationalization.</p>
<p>Some perhaps useful calls that weren't implemented:</p>
<ul>
<li>NuWhatIsThisFile(char* filename).&nbsp; Returns an enum indicating what we
think is in the file, i.e. SHK, BXY, BSE, BQY, BNY, SEA, ZIP, ZOO, ARC, TAR,
.gz, .Z, etc.&nbsp; Useful for an application that supports more than one
kind of archive.&nbsp; Not sure if it belongs in NufxLib.&nbsp; (Right now
the &quot;open archive&quot; calls will return a &quot;hey, this is Binary
II&quot; error code; this mechanism could be extended.)</li>
<li>NuRearrangeArchive(...args...).&nbsp; There is no way to sort an archive
short of reconstructing it manually.&nbsp; An interface for swapping the
positions of two records could be provided.&nbsp; This would allow sorting
an archive.</li>
<li>NuExtractThreadChunk(len).&nbsp; NufxLib allows you to extract an entire
thread to a buffer or a file, but doesn't let you extract small pieces and
handle them yourself.&nbsp; It's an all-or-nothing grab.&nbsp; Since we're
dealing with Apple II files, which tend to be relatively small, this
shouldn't cause problems.&nbsp; It can be inconvenient though to have to
grab large chunks of memory to apply a filter before writing data.&nbsp; It
might make sense to define a DataSink with a callback function (instead of a
buffer or FP) that is expected to write the data.</li>
</ul>
<hr>
<h2><a name="history"></a><u>History</u></h2>
<p>A brief history of NufxLib releases.&nbsp; See &quot;ChangeLog.txt&quot; in
the sources for more detail.</p>
<table border="1" width="90%">
<tr>
<td width="15%"><b>Version</b></td>
<td width="15%"><b>Date</b></td>
<td width="70%"><b>Comments</b></td>
</tr>
<tr>
<td width="15%">v0.0</td>
<td width="15%">mid-1998</td>
<td width="70%">Work begins</td>
</tr>
<tr>
<td width="15%">v0.1</td>
<td width="15%">2000/01/17</td>
<td width="70%">First version viewed by test volunteers.</td>
</tr>
<tr>
<td width="15%">v0.5</td>
<td width="15%">2000/02/09</td>
<td width="70%">Alpha test version.</td>
</tr>
<tr>
<td width="15%">v0.6</td>
<td width="15%">2000/03/05</td>
<td width="70%">Beta test version.</td>
</tr>
<tr>
<td width="15%">v1.0</td>
<td width="15%">2000/05/18</td>
<td width="70%">Initial release.</td>
</tr>
<tr>
<td width="15%">v1.0.1</td>
<td width="15%">2000/05/22</td>
<td width="70%">Added workaround for badly-formed archives.</td>
</tr>
<tr>
<td width="15%">v1.1</td>
<td width="15%">2002/10/20</td>
<td width="70%">Many new features, notably support for several compression
formats.</td>
</tr>
<tr>
<td width="15%">v2.0</td>
<td width="15%">2003/03/18</td>
<td width="70%">Support for Win32 DLL features.</td>
</tr>
<tr>
<td width="15%">v2.0.1</td>
<td width="15%">2003/10/16</td>
<td width="70%">Added junk-skipping and a workaround for bad option lists;
Mac OS X stuff from sheppy.</td>
</tr>
<tr>
<td width="15%">v2.0.2</td>
<td width="15%">2004/03/10</td>
<td width="70%">Handle zeroed MasterEOF, and correctly set permissions on
&quot;locked&quot; files.</td>
</tr>
<tr>
<td width="15%">v2.0.3</td>
<td width="15%">2004/10/11</td>
<td width="70%">Fixed some obscure bugs that CiderPress was hitting.</td>
</tr>
<tr>
<td width="15%">v2.1.0</td>
<td width="15%">2005/09/17</td>
<td width="70%">Added kNuValueIgnoreLZW2Len.</td>
</tr>
<tr>
<td width="15%">v2.1.1</td>
<td width="15%">2006/02/18</td>
<td width="70%">Fix two minor bugs.</td>
</tr>
<tr>
<td width="15%">v2.2.0</td>
<td width="15%">2007/02/19</td>
<td width="70%">Switched to BSD license.&nbsp; Identify &quot;bad Mac&quot;
archives automatically.</td>
<tr>
<td width="15%">v2.2.2</td>
<td width="15%">2014/10/30</td>
<td width="70%">Updated build files, especially for Win32.&nbsp; Moved to
github.</td>
</tr>
</tr>
<tr>
<td width="15%">v3.0.0</td>
<td width="15%">2015/01/09</td>
<td width="70%">Source code overhaul.&nbsp; Added Unicode filename handling.</td>
</tr>
<tr>
<td width="15%">v3.1.0</td>
<td width="15%">2017/09/21</td>
<td width="70%">Improvements to Mac OS X attribute handling. Minor fixes.</td>
</tr>
</table>
&nbsp;
<hr>
<h2><a name="acknowledgements"></a><u>Acknowledgements</u></h2>
<p>I'd like to thank Eric Shepherd for participating in some ping-pong e-mail
sessions while I tried to get autoconf, BeOS, and some crufty versions of &quot;make&quot;
figured out for v1.0.</p>
<hr>
<p>This document is Copyright &copy; 2000-2017 by <a href="http://www.fadden.com/">Andy
McFadden</a>.&nbsp; All Rights Reserved.</p>
<p>The latest version can be found on the NuLib web site at
<a href="http://www.nulib.com/">http://www.nulib.com/</a>.</p>
</td></tr></table></td></tr></table></td></tr></table></td></tr></table></td></tr></table><!--msnavigation--></td></tr><!--msnavigation--></table></body>
</html>