// // ARCHIVEB.CPP // // Source file for ArchiveLib 1.0 // // Copyright (c) Greenleaf Software, Inc. 1994 // All Rights Reserved // // CONTENTS // // ALArchiveBase::operator new() // ALArchiveBase::ALArchiveBase() // ALArchiveBase::~ALArchiveBase() // ALArchiveBase::SetComment() // ALArchiveBase::WriteDirectory() // ALArchiveBase::ScanStatus() // ALArchiveBase::Extract() // ALArchiveBase::AddJobs() // ALArchiveBase::AddDirectoryEntries() // ALArchiveBase::CalculateJobSize() // ALArchiveBase::CalculateCompressedJobSize() // ALArchiveBase::Create(ALEntryList&) // ALArchiveBase::CopyJobs() // ALArchiveBase::Create( ALArchiveBase&,ALEntryList&) // ALArchiveBase::Append(ALEntryList&) // ALArchiveBase::Append(ALArchiveBase&,ALEntryList&) // ALArchiveBase::ReadDirectory() // ALArchiveBase::WriteArchiveData() // ALArchiveBase::ReadArchiveData() // ALArchiveBase::Delete(ALEntryList&,ALArchiveBase&) // ALArchiveBase::FillListBox() // // // DESCRIPTION // // This file contains all of the source code for the base class, // ALArchiveBase. Classes derived from ALArchiveBase don't actually // do much work, they just bind different sorts of storage objects and // compression engines to an application, so this is where all the action // is. The details on how things get inserted and extracted from an // archive will all be found here. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // // #include "arclib.h" #pragma hdrstop #include "_openf.h" // // void * ALArchiveBase::operator new( size_t size ) // // ARGUMENTS: // // size : The number of bytes needed to create a new ALArchiveBase object. // // RETURNS // // A pointer to the newly allocated storage area, or 0 if no storage // was available. // // DESCRIPTION // // When using a DLL, it is easy to enter a dangerous situation when // creating objects whose ctor and dtor are both in the DLL. The problem // arises because when you create an object using new, the memory for // the object will be allocated from the EXE. However, when you destroy // the object using delete, the memory is freed inside the DLL. Since // the DLL doesn't really own that memory, bad things can happen. // // But, you say, won't the space just go back to the Windows heap regardless // of who tries to free it? Maybe, but maybe not. If the DLL is using // a subsegment allocation scheme, it might do some sort of local free // before returning the space to the windows heap. That is the point where // you could conceivably cook your heap. // // By providing our own version of operator new inside this class, we // ensure that all memory allocation for the class will be done from // inside the DLL, not the EXE calling the DLL. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // #if defined( AL_BUILDING_DLL ) void AL_DLL_FAR * AL_PROTO ALArchiveBase::operator new( size_t size ) { return ::new char[ size ]; } #endif // // ALArchiveBase::ALArchiveBase( ALStorage *storage_object, // short int delete_in_dtor ) // // ARGUMENTS: // // storage_object : A pointer to the storage object that will/does // hold the archive. // // delete_in_dtor : This flags whether the ALArchiveBase object should call // the destructor for the storage object when the // ALArchiveBase object is created. // // RETURNS // // Nothing, it is a constructor. // // DESCRIPTION // // This is the ALArchiveBase constructor. It is a public member function, // but in practice it should only be called by the constructors for // class derived from ALArchiveBase. Since there are pure functions // in this class, you can't construct an object of this type anyway, // no matter how hard you try. // // Despite the complexity of this class, and the vast array of member // functions it contains, the constructor doesn't have much to do. It // just sets up the contents of a few data members, and that's that. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // AL_PROTO ALArchiveBase::ALArchiveBase( ALStorage AL_DLL_FAR *storage_object, short int delete_in_dtor ) : miDeleteStorageObject( delete_in_dtor ) { mpArchiveStorageObject = storage_object; mszComment = 0; mlDirectoryOffset = -1L; miVersion = -1; } // // ALArchiveBase::~ALArchiveBase() // // ARGUMENTS: // // None. // // RETURNS // // None, destructors don't get any. // // DESCRIPTION // // The destructor for ALArchiveBase has a few pieces of busy work to // complete. First, it might have a comment to delete. Second, it // might have to delete its storage object, but only if it was told // to in the constructor. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // AL_PROTO ALArchiveBase::~ALArchiveBase() { AL_ASSERT( GoodTag(), "~Archive(): Attempting to delete invalid ALArchiveBase" ); if ( mszComment ) delete[] mszComment; if ( mpArchiveStorageObject && miDeleteStorageObject ) delete mpArchiveStorageObject; AL_ASSERT( GoodTag(), "~Archive::Attempting to delete invalid ALArchiveBase" ); } // // int ALArchiveBase::SetComment( char * comment ) // // ARGUMENTS: // // comment : The new comment that is going to be attached to the archive. // // RETURNS // // AL_SUCCESS, if things went well, AL_CANT_ALLOCATE_MEMORY if allocation // of the character array failed. // // DESCRIPTION // // The archive object has a comment member, that is blank when first // constructed. It can be set to something interesting either by // reading in a new comment along with the archive directory, or by // setting it using this function. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::SetComment( char AL_DLL_FAR * comment ) { if ( mszComment ) delete[] mszComment; if ( comment == 0 ) mszComment = 0; else { mszComment = new char[ strlen( comment ) + 1 ]; if ( mszComment ) strcpy( mszComment, comment ); else return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Failed to allocate memory for " "comment in archive %s", mpArchiveStorageObject->mName.GetName() ); } return mStatus; } // // int ALArchiveBase::WriteDirectory( ALEntryList &list ) // // ARGUMENTS: // // list : The ALEntryList object that contains the Archive's // up to date directory. // // RETURNS // // The integer stored in mStatus, which ought to be AL_SUCCESS if everything // went okay, or some int < AL_SUCCESS on error. // // DESCRIPTION // // This is the public function the user can call to rewrite the directory // for an archive. It is also called internally be several of the functions // the update archive contents. Probably the main reason to call this under // normal circumstances would be after modifying the comment field of an // archive. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // // // Don't call ArchiveOperation() here, because this might // just be a component of a directory write (during an append, eg.) // int AL_PROTO ALArchiveBase::WriteDirectory( ALEntryList AL_DLL_FAR &list ) { ALOpenInputFile archive( *mpArchiveStorageObject ); mpArchiveStorageObject->Seek( mlDirectoryOffset ); mpArchiveStorageObject->WritePortableShort( miVersion ); WriteArchiveData(); mpArchiveStorageObject->WriteString( mszComment ); AddDirectoryEntries( list ); return mStatus; } // PRIVATE MEMBER FUNCTION // // void ALArchiveBase::ScanStatus( ALEntryList &list ) // // ARGUMENTS: // // list : The list of entries that have just been processed. // // RETURNS // // None. This function sort of has a return, it will update // the member mStatus with an error code if one is found. // // DESCRIPTION // // // After an archive operation, I use this function to update the // status member of the archive. If the archive doesn't already // have an error, I check through all the storage objects and // compression engines to see if any of them hosed up. Any error // of any sort by any of them is copied into the archive status. // The whole point of this is to ensure that if // ALArchiveBase.mStatus == AL_SUCCESS, it means everything worked. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // void AL_PROTO ALArchiveBase::ScanStatus( ALEntryList AL_DLL_FAR &list ) { if ( mStatus < AL_SUCCESS ) return; ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->mpStorageObject->mStatus < AL_SUCCESS ) { mStatus.SetError( job->mpStorageObject->mStatus, "%s: %s", job->mpStorageObject->mName.GetSafeName(), job->mpStorageObject->mStatus.GetStatusDetail() ); return; } job = job->GetNextEntry(); } } // // int ALArchiveBase::Extract( ALEntryList &list ) // // ARGUMENTS: // // list : A list of storage objects to be extracted (if marked.) // // RETURNS // // AL_SUCCESS if all went well, or < AL_SUCCESS if the process // went sour at any point. // // DESCRIPTION // // This function is one of the high level functions that can be called // from a user program. It has several important things it needs to do // in order to extract the appropriate objects from an archive: // // o Flag any duplicates. We don't extract anything twice, that will // be considered an error. // // o Open the Archive storage object. // // o Calculate the total number of bytes to be processed in the entire // job, and give this information to the monitor, who might care if // he is in AL_MONITOR_JOB mode. // // o Iterate through the list, performing the following actions for // each object marked for extraction: // // o Update the monitor with information about the object destined // for immediate extraction. // // o Locate the compressed object in the archive. // // o Decompress the object, and check for CRC errors. // // o Update the monitor. // // o After all objects have been extracted, update the monitor again. // // o Scan for extraction errors, then return the result. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::Extract( ALEntryList AL_DLL_FAR &list ) { // // Open the input storage object, if not already open. Let the monitor // know about it. // ALOpenInputFile archive( *mpArchiveStorageObject ); list.mrMonitor.ArchiveOperation( AL_ARCHIVE_OPEN, this, 0 ); // // Get rid of any duplicate entries, and set up the monitor sizes. // list.UnmarkDuplicates( list, "Duplicate entry in list passed to Extract()" ); list.mrMonitor.mlJobSoFar = 0L; if ( list.mrMonitor.miMonitorType == AL_MONITOR_JOB ) list.mrMonitor.mlJobSize = CalculateCompressedJobSize( list ); // // This loop iterates through the entire ALEntryList. We only care about // ALEntry objects that have their mark set. // ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->miMark ) { // // Go to the correct input position in this, and set up the monitor for // this particular object. // list.mrMonitor.ArchiveOperation( AL_EXTRACTION_OPEN, this, job ); mpArchiveStorageObject->Seek( job->mlCompressedObjectPosition ); list.mrMonitor.mlObjectStart = job->mlCompressedObjectPosition; list.mrMonitor.mlObjectSize = job->mlCompressedSize; mpArchiveStorageObject->mpMonitor = &list.mrMonitor; // // Extract it, then check the CRC. // job->mpCompressionEngine->Decompress( *mpArchiveStorageObject, *job->mpStorageObject, job->mlCompressedSize ); if ( job->mpStorageObject->GetCrc32() != job->GetCrc32() ) job->mpStorageObject->mStatus.SetError( AL_CRC_ERROR, "CRC32 was supposed to be %08lx, was %08lx", job->GetCrc32(), job->mpStorageObject->GetCrc32() ); // // Update the monitor data, and yield some time. Note that I turn off // the monitor at this point so it doesn't jump around while I seek to the // next position in the archive. // list.mrMonitor.mlJobSoFar += job->mlCompressedSize; mpArchiveStorageObject->YieldTime(); mpArchiveStorageObject->mpMonitor = 0; list.mrMonitor.ArchiveOperation( AL_EXTRACTION_CLOSE, this, job ); job->mpStorageObject->mpMonitor = 0; } job = job->GetNextEntry(); } // // Update the monitor, then scan the list for status errors. // list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); ScanStatus( list ); return mStatus; } // PRIVATE MEMBER FUNCTION // // int ALArchiveBase::AddJobs( ALEntryList &list ) // // ARGUMENTS: // // list : A list of marked objects to be added to the archive. // // RETURNS // // AL_SUCCESS if things are going well, < AL_SUCCESS in case of error. // // DESCRIPTION // // This is a helper function that is called by both Create() and // Append(). There is enough code here to justify breaking this // out into a separate module. // // All this guy does is sit in a loop, look for marked entries in the // list, and compress each one into the archive. Before it adds each object // to the archive, it has to set up the monitor so that progress on the // selected object will be monitored properly. It has to dink with the // monitor once again when the object has been compressed. It relies on // the calling function to have set up the total job size and other info // that the monitor might need. It also has to set up some of the // data in the ALEntry object for each job, as not all of this information // is available until *after* the job has been compressed. For example, // the storage object's CRC32 gets calculated as a byproduct of the // compression process. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::AddJobs( ALEntryList AL_DLL_FAR &list ) { list.mrMonitor.mlObjectStart = 0L; // This will be true for all input jobs // // This loop iterates through all of the entries in the list, picking off // only the marked entries. // ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->miMark ) { // // We need to keep track of the position in the archive where the compressed // data is going to go. // job->mlCompressedObjectPosition = mpArchiveStorageObject->Tell(); // // Attach the monitor to the storage object that is going to be inserted // in the archive. // list.mrMonitor.ArchiveOperation( AL_INSERTION_OPEN, this, job ); list.mrMonitor.mlObjectSize = -1L; // This means we ask for it in ALMonitor, after the object is opened job->mpStorageObject->mpMonitor = &list.mrMonitor; // // Compress the object into the archive. Then store the resulting CRC // the compressed size in the ALEntry object. // job->mpCompressionEngine->Compress( *job->mpStorageObject, *mpArchiveStorageObject ); job->mlCrc32 = job->mpStorageObject->GetCrc32(); job->mpStorageObject->mpMonitor = 0; if ( job->mpCompressionEngine->mStatus < 0 ) return mStatus = job->mpCompressionEngine->mStatus; job->mlCompressedSize = mpArchiveStorageObject->Tell() - job->mlCompressedObjectPosition; // // Update the monitor // list.mrMonitor.mlJobSoFar += job->mpStorageObject->GetSize(); list.mrMonitor.ArchiveOperation( AL_INSERTION_CLOSE, this, job ); } job = job->GetNextEntry(); if ( mStatus < 0 ) break; } return mStatus; } // PRIVATE MEMBER FUNCTION // // int ALArchiveBase::AddDirectoryEntries( ALEntryList &list ) // // ARGUMENTS: // // list : The list of ALEntry objects to be written to the directory. // // RETURNS // // AL_SUCCESS if everything goes well, < AL_SUCCESS otherwise. // // DESCRIPTION // // This function writes all the entries in the list to the Archive // directory. It doesn't do a seek() to the start of the directory, // so the calling routine needs to be absolutely sure that it is in // the write spot when it invokes this. // // This routine leaves the output pointer of the storage object pointing // at just the right spot to write some more entries. That means you can // call this function repeatedly as new entries are added to the list. // The function also terminates the directory properly, so that if you // don't add any more directory entries, the archive is still ready // for primetime. // // This function is called by WriteDirectory(), and both versions of // Append(). // // Writing directory entries is a real simple linear task. The source // code here should explain it all. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // // // No call to ArchiveOperation here, either. The setup and everything // else has to be done by the calling routine. // int AL_PROTO ALArchiveBase::AddDirectoryEntries( ALEntryList AL_DLL_FAR &list ) { ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->miMark ) { mpArchiveStorageObject->WriteString( job->mpStorageObject->mName.GetSafeName() ); mpArchiveStorageObject->WriteChar( job->mpCompressionEngine->miCompressionType ); job->mpCompressionEngine->WriteEngineData( mpArchiveStorageObject ); mpArchiveStorageObject->WriteChar( job->mpStorageObject->miStorageObjectType ); job->mpStorageObject->WriteStorageObjectData( mpArchiveStorageObject ); mpArchiveStorageObject->WritePortableLong( job->mpStorageObject->GetSize() ); mpArchiveStorageObject->WritePortableLong( job->GetCompressedSize() ); mpArchiveStorageObject->WritePortableLong( job->GetCrc32() ); mpArchiveStorageObject->WritePortableLong( job->mlCompressedObjectPosition ); mpArchiveStorageObject->WriteString( job->GetComment() ); mpArchiveStorageObject->WritePortableLong( job->mpStorageObject->mTimeDate.GetUnixTime() ); mpArchiveStorageObject->WritePortableShort( job->mpStorageObject->mAttributes.PackedAttributes() ); if ( mpArchiveStorageObject->mStatus < 0 ) return mStatus = mpArchiveStorageObject->mStatus; } job = job->GetNextEntry(); } // // I write out the end of directory string here. But then I back up the // file pointer so new entries can be appended without causing any trouble // The end of the directory is denoted by an entry with an empty name. // mpArchiveStorageObject->WriteString( "" ); mpArchiveStorageObject->Seek( mpArchiveStorageObject->Tell() - 2 ); return AL_SUCCESS; } // PRIVATE MEMBER FUNCTION // // long ALArchiveBase::CalculateJobSize( ALEntryList &list ) // // ARGUMENTS: // // list : The list of entries in the job. // // RETURNS // // This function is used to calculate the total number of bytes that // are going to have to be moved when performing a Create() or Append() // operation. We need that info in order to set up a monitor properly // when its mode is AL_MONITOR_JOB. Naturally, we don't really care // about the total size when the monitor is in AL_MONITOR_OBJECTS mode. // Anyway, it returns the total size of all the objects. // // DESCRIPTION // // If a monitor is running in AL_MONITOR_JOB mode, we need to add up // the sizes of all the storage objects we are going to process, so // that we can accurately track our progress from 0 to 100%. In many // cases, the sizes of all the files will not yet be known, which means // this routine will have to open the files up and check the values. // That is why we only call this routine when we have to. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // long AL_PROTO ALArchiveBase::CalculateJobSize( ALEntryList AL_DLL_FAR &list ) { long total = 0; ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->miMark ) { long obj_size; if ( ( obj_size = job->mpStorageObject->GetSize() ) == -1 ) { job->mpStorageObject->Open(); obj_size = job->mpStorageObject->GetSize(); job->mpStorageObject->Close(); if ( obj_size == -1 ) return -1; } total += obj_size; } job = job->GetNextEntry(); } return total; } // PRIVATE MEMBER FUNCTION // // long ALArchiveBase::CalculateCompressedJobSize( ALEntryList &list ) // // ARGUMENTS: // // list : The list of compressed jobs to be processed. // // RETURNS // // The total size of a bunch of compressed objects, not the uncompressed // size. // // DESCRIPTION // // When we are monitoring an Extract() command, the monitor object // gets attached to the Archive, not to the objects that are getting // sucked out of it. This means that progress is being measured // against the compressed objects, not the true size objects. So // before I start the extract, I call this function to see just how // much compressed space is taken up by the compressed objects in // the archive. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // long AL_PROTO ALArchiveBase::CalculateCompressedJobSize( ALEntryList AL_DLL_FAR &list ) { long total = 0; ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->miMark ) { if ( job->mlCompressedSize == -1 ) return -1; else total += job->mlCompressedSize; } job = job->GetNextEntry(); } return total; } // // int ALArchiveBase::Create( ALEntryList &list ) // // ARGUMENTS: // // list : A list of ALEntry objects describing what is going to // be stuffed into the archive. // // RETURNS // // AL_SUCCESS if things went well, mStatus < 0 ) return mStatus = mpArchiveStorageObject->mStatus; // // We don't want to create an archive with duplicate entries, so we check here. // list.UnmarkDuplicates( list, "Duplicate entry in list passed to Create()" ); // // The first four bytes in the archive are a long that points to the // first byte of the directory. I don't know where the directory is // going to be, so I just reserve space at this time with a dummy value. // mpArchiveStorageObject->WritePortableLong( 0x12345678L ); // // Set up the monitor. // list.mrMonitor.mlJobSoFar = 0L; if ( list.mrMonitor.miMonitorType == AL_MONITOR_JOB ) list.mrMonitor.mlJobSize = CalculateJobSize( list ); // // AddJobs() takes care of actually adding the jobs to the archive. // AddJobs( list ); // // All the jobs are written, now I can figure out where the // directory is in the storage object. I copy it, then write // it out to the storage object at position 0. // mlDirectoryOffset = mpArchiveStorageObject->Tell(); mpArchiveStorageObject->Seek( 0L ); mpArchiveStorageObject->WritePortableLong( mlDirectoryOffset ); // // Return without writing the directory if there is an error in the // archive storage object. // if ( mpArchiveStorageObject->mStatus < 0 ) { list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus = mpArchiveStorageObject->mStatus; } // // Finally, write out the directory to the storage object. // list.mrMonitor.ArchiveOperation( AL_START_DIRECTORY_WRITE, this, 0 ); WriteDirectory( list ); // // Update the monitor, check for errors, and blow. // list.mrMonitor.ArchiveOperation( AL_END_DIRECTORY_WRITE, this, 0 ); list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); ScanStatus( list ); return mStatus; } // PRIVATE MEMBER FUNCTIONS // // int ALArchiveBase::CopyJobs( ALArchiveBase & source_archive, // ALEntryList & source_list ) // // ARGUMENTS: // // source_archive : The source for all the ALEntry objects that are // going to get copied to this. // // source_list : The list of ALEntry objects that are going to be copied. // // RETURNS // // An mStatus value, either AL_SUCCESS or < AL_SUCCESS. // // DESCRIPTION // // This private member function is used by the Create() and Append() member // functions. Each of these two public functions has two versions, one // which compresses freestanding storage objects into an archive, and // another which copies jobs out of one archive and into this. The // second versions of the two functions use CopyJobs() to get the // compressed objects out of one archive and put it into this. // // The actual operation of this guy is pretty simple. It would be almost // trivial without having to take the monitor into account. Basically, // it just has to work its way through the list of entries. For each // marked entry, we just seek to the correct position in the input file, // the copy the correct number of bytes to this. // // One thing kind of funny here is that the ALEntryList starts off with // offsets for the objects within the source archive. But after copying them // over, we change the offset field in the ALEntry object to reflect the new // position in this. This means that after this function has completed, // you ALEntryList object is no longer associated with source_archive, it // is instead associated with this. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::CopyJobs( ALArchiveBase AL_DLL_FAR &source_archive, ALEntryList AL_DLL_FAR &source_list ) { // // Open the storage object attached to the input archive. The storage object // attached to this is already open. // ALOpenInputFile input( *(source_archive.mpArchiveStorageObject) ); // // Iterate through the list of entries in the list, selecting only the // marked entries. // ALEntry *job = source_list.GetFirstEntry(); while ( job ) { if ( job->miMark ) { // // Seek the compressed object in the source archive, then update the monitor // to work properly during the copy operation. // source_archive.mpArchiveStorageObject->Seek( job->mlCompressedObjectPosition ); source_list.mrMonitor.mlObjectStart = job->mlCompressedObjectPosition; source_list.mrMonitor.mlObjectSize = job->mlCompressedSize; source_list.mrMonitor.ArchiveOperation( AL_COPY_OPEN, this, job ); source_archive.mpArchiveStorageObject->mpMonitor = &source_list.mrMonitor; // // Save the new position in the destination archive, then copy the // whole thing across. // job->mlCompressedObjectPosition = mpArchiveStorageObject->Tell(); for ( long i = 0 ; i < job->mlCompressedSize ; i++ ) { int c = source_archive.mpArchiveStorageObject->ReadChar(); mpArchiveStorageObject->WriteChar( c ); } // // Update the monitor now that the copy is complete. // source_list.mrMonitor.ArchiveOperation( AL_COPY_CLOSE, this, job ); source_archive.mpArchiveStorageObject->YieldTime(); source_list.mrMonitor.mlJobSoFar += job->mlCompressedSize; source_archive.mpArchiveStorageObject->mpMonitor = 0; if ( source_archive.mpArchiveStorageObject->mStatus < 0 ) return mStatus = source_archive.mpArchiveStorageObject->mStatus; if ( mpArchiveStorageObject->mStatus < 0 ) return mStatus = mpArchiveStorageObject->mStatus; } job = job->GetNextEntry(); if ( mStatus < 0 ) break; } return mStatus; } // // int ALArchiveBase::Create( ALArchiveBase &source_archive, // ALEntryList &source_list ) // // ARGUMENTS: // // source_archive : The archive that contains the compressed objects // we are using to create this. // // source_list : The ALEntryList that contains the marked ALEntry // objects that are going to be inserted in this. // // RETURNS // // AL_SUCCESS if things went well, < AL_SUCCESS to flag an error. // // DESCRIPTION // // This is the second version of Create(). Instead of creating a new // archive by using a bunch of freestanding objects, this guy just // sucks existing compressed objects out of one archive and copies // them directly into another. The actual copying gets done in // CopyJobs(). // // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::Create( ALArchiveBase AL_DLL_FAR &source_archive, ALEntryList AL_DLL_FAR &source_list ) { // // Open the source archive, set the version, and blow if for some reason // the storage object I am writing to isn't working right. // ALOpenOutputFile archive( *mpArchiveStorageObject ); miVersion = 0x100; if ( mpArchiveStorageObject->mStatus < 0 ) return mStatus = mpArchiveStorageObject->mStatus; // // I don't want to create an archive with duplicates, that would be bad. // source_list.UnmarkDuplicates( source_list, "Duplicate entry in list passed to Create()" ); // // At this point, just for fun, I am going to calculate the total // compressed size of the jobs I am copying. Hey, it looks like I // could substitute a call to CalculateCompressedSize() here! // source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_OPEN, this, 0 ); source_list.mrMonitor.mlJobSoFar = 0L; source_list.mrMonitor.mlJobSize = 0L; for ( ALEntry *job = source_list.GetFirstEntry(); job != 0; job = job->GetNextEntry() ) { if ( job->GetMark() ) source_list.mrMonitor.mlJobSize += job->mlCompressedSize; } // // Since I am creating a new archive, I write a long out as a place // holder for the directory pointer. When I am done copying jobs, // I'll come back here and write a pointer to the directory. // mpArchiveStorageObject->WritePortableLong( 0x12345678L ); // // Now copy the data. // CopyJobs( source_archive, source_list ); // // Write out the directory offset, then the directory itself. // mlDirectoryOffset = mpArchiveStorageObject->Tell(); mpArchiveStorageObject->Seek( 0L ); mpArchiveStorageObject->WritePortableLong( mlDirectoryOffset ); if ( mpArchiveStorageObject->mStatus < 0 ) { source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus = mpArchiveStorageObject->mStatus; } source_list.mrMonitor.ArchiveOperation( AL_START_DIRECTORY_WRITE,this, 0 ); WriteDirectory( source_list ); source_list.mrMonitor.ArchiveOperation( AL_END_DIRECTORY_WRITE, this, 0 ); source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); // // Update the error status, and then we are done. // ScanStatus( source_list ); return mStatus; } // // int ALArchiveBase::Append( ALEntryList &list ) // // ARGUMENTS: // // list : A list of objects to append to this. // // RETURNS // // AL_SUCCESS if things go okay, < AL_SUCCESS if they didn't. // // DESCRIPTION // // This routine is one of the public functions. It is called to add // a list of standalone objects to an existing archive, this. // To accomplish this, we have to read in the existing directory, then // add the new batch of objects to the archive. Finally, I write out // the old directory, then the directory for the new batch of objects. // // There is another version of Append() that takes as input a list of // entries that are in another archive. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::Append( ALEntryList AL_DLL_FAR &list ) { ALEntryList old_list; // // Open the storage object for this. // ALOpenInputFile archive( *mpArchiveStorageObject ); // // I read in the current directory for this archive. I am going to // write over the directory with new stuff, so I will have to write it // back out later. // ReadDirectory( old_list ); if ( mStatus < 0 ) return mStatus; // // The list of new objects I am going to add needs to be scanned for // duplicates. First I clear duplicate entries from the list itself. // Then I clear any duplicates between the current list and the // stuff already in the archive. // list.UnmarkDuplicates( list, "Duplicate entry in list passed to Append()" ); list.UnmarkDuplicates( old_list, "Duplicate entry in list passed to Append()" ); // // I get the monitor set up, for the batch of entries I am about to do. // list.mrMonitor.ArchiveOperation( AL_ARCHIVE_OPEN, this, 0 ); list.mrMonitor.mlJobSoFar = 0L; if ( list.mrMonitor.miMonitorType == AL_MONITOR_JOB ) list.mrMonitor.mlJobSize = CalculateJobSize( list ); // // The new entries start at the position currently occupied by the // directory. I seek to that point, then call AddJobs() to do the // dirty work. // mpArchiveStorageObject->Seek( mlDirectoryOffset ); AddJobs( list ); if ( mStatus < 0 ) { list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus; } // // Now that all the new stuff is in the archive, I can figure // out where the directory belongs, and write it out to position // 0 in the archive. // mlDirectoryOffset = mpArchiveStorageObject->Tell(); mpArchiveStorageObject->Seek( 0L ); mpArchiveStorageObject->WritePortableLong( mlDirectoryOffset ); if ( mpArchiveStorageObject->mStatus < 0 ) { list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus = mpArchiveStorageObject->mStatus; } // // Now I write the old directory out, and then add in the new // directory entries. // list.mrMonitor.ArchiveOperation( AL_START_DIRECTORY_WRITE, this, 0 ); WriteDirectory( old_list ); AddDirectoryEntries( list ); // // Update the monitor, check for errors, then leave. // list.mrMonitor.ArchiveOperation( AL_END_DIRECTORY_WRITE, this, 0 ); list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); ScanStatus( list ); return mStatus; } // // int ALArchiveBase::Append( ALArchiveBase &source_archive, // ALEntryList &source_list ) // // ARGUMENTS: // // source_archive : The archive where the objects that we are going // use can be found. // // source_list : The ALEntryList that has a batch of marked entries. // // RETURNS // // AL_SUCCESS if things work, < AL_SUCCESS if they don't. // // DESCRIPTION // // This append function works just like the previous one, except it // is appending jobs that have already been compressed and can be found // in a different archive. It has to go through exactly the same process, // which consists of reading the current directory in from this, appending // the new compressed objects to this, then writing out the old directory // and the new list. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::Append( ALArchiveBase AL_DLL_FAR &source_archive, ALEntryList AL_DLL_FAR &source_list ) { ALEntryList old_list; // // Open the storage object associated with this. // ALOpenInputFile archive( *mpArchiveStorageObject ); source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_OPEN, this, 0 ); // // I have to read the current directory into memory, because as soon as // I start to write objects out to this, I am going to obliterate // the directory. // ReadDirectory( old_list ); if ( mStatus < 0 ) { source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus; } // // We don't want to create an archive that has duplicate entries, that would // be a bad thing. So I first comb all the duplicate entries out of the list // of objects to append. I then compare that list for duplicates against // the list of objects already in the library, and comb out any matches // there as well. // source_list.UnmarkDuplicates( source_list, "Duplicate entry in list passed to Append()" ); source_list.UnmarkDuplicates( old_list, "Duplicate entry in list passed to Append()" ); // // I am going to start writing new stuff at the location where the // the directory starts right now. // mpArchiveStorageObject->Seek( mlDirectoryOffset ); // // Before starting to copy jobs, I have to set up the monitor. // This includes calculating the total number of compressed bytes // in all the marked jobs. I could do this a lot easier by calling // the CalculateCompressedBytes() function, but it's too late to change now. // source_list.mrMonitor.mlJobSoFar = 0L; source_list.mrMonitor.mlJobSize = 0L; for ( ALEntry *job = source_list.GetFirstEntry(); job != 0; job = job->GetNextEntry() ) { if ( job->GetMark() ) source_list.mrMonitor.mlJobSize += job->mlCompressedSize; } // // CopyJobs() does the hard work for me. // CopyJobs( source_archive, source_list ); if ( mStatus < 0 ) { source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus; } // // The jobs are now in, I just have to update the directory with // the old files and the new files. // mlDirectoryOffset = mpArchiveStorageObject->Tell(); mpArchiveStorageObject->Seek( 0L ); mpArchiveStorageObject->WritePortableLong( mlDirectoryOffset ); if ( mpArchiveStorageObject->mStatus < 0 ) { source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); return mStatus = mpArchiveStorageObject->mStatus; } source_list.mrMonitor.ArchiveOperation( AL_START_DIRECTORY_WRITE, this, 0 ); WriteDirectory( old_list ); AddDirectoryEntries( source_list ); // // Wrap it up. // source_list.mrMonitor.ArchiveOperation( AL_END_DIRECTORY_WRITE, this, 0 ); source_list.mrMonitor.ArchiveOperation( AL_ARCHIVE_CLOSE, this, 0 ); ScanStatus( source_list ); return mStatus; } // // int ALArchiveBase::ReadDirectory( ALEntryList &list ) // // ARGUMENTS: // // list : The target for the directory listing. // // RETURNS // // AL_SUCCESS or < AL_SUCCESS if things don't work. // // DESCRIPTION // // This function reads the directory from archive this and places the // results in the list parameter. I have to apologize for the fact // that it is so long. The only thing I can say in my defense is that // even though it is really long, it is also really simple, not tricky // bits here. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::ReadDirectory( ALEntryList AL_DLL_FAR &list ) { list.mrMonitor.ArchiveOperation( AL_START_DIRECTORY_READ, this, 0 ); ALOpenInputFile archive( *mpArchiveStorageObject ); if ( mpArchiveStorageObject->mStatus < 0 ) return mStatus = mpArchiveStorageObject->mStatus; // // First I seek to the start of the directory (offset found at 0), and // read in the version. This function only supports the directory // structure defined in version 0x100. // mpArchiveStorageObject->Seek( 0 ); mpArchiveStorageObject->ReadPortableLong( mlDirectoryOffset ); mpArchiveStorageObject->Seek( mlDirectoryOffset ); mpArchiveStorageObject->ReadPortableShort( miVersion ); if ( miVersion != 0x100 ) return mStatus.SetError( AL_INVALID_ARCHIVE, "%s is not a valid archive file", mpArchiveStorageObject->mName.GetSafeName() ); // // Read in any customized archive data defined by a derived class. // ReadArchiveData(); // // Read in the comment, deleting the old one if necessary. // if ( mszComment ) delete[] mszComment; mszComment = mpArchiveStorageObject->ReadString(); // // Now, the big loop. I have to read in each entry, one at a time, and // add it to the list. If I broke this out into a separate routine it // would make the whole thing a lot more manageable. // for ( ; ; ) { if ( mpArchiveStorageObject->mStatus < 0 ) return mStatus = mpArchiveStorageObject->mStatus; char *name = mpArchiveStorageObject->ReadString(); if ( name == 0 ) break; // // The directory ends with a blank name. // if ( strlen( name ) == 0 ) { delete[] name; break; } // // Derived classes are responsible for providing a version of // CreateCompressionEngine() that will convert the engine_type // integer into a created compression engine. The derived class is // then also responsible for reading in the engine data from the archive. // int engine_type = mpArchiveStorageObject->ReadChar(); ALCompressionEngine *engine = CreateCompressionEngine( engine_type ); if ( engine ) engine->ReadEngineData( mpArchiveStorageObject ); else { char *temp = mpArchiveStorageObject->ReadString(); if ( temp ) delete[] temp; return mStatus.SetError( AL_CANT_CREATE_ENGINE, "Failure creating compression engine for object %s", name ); } // // Now we go through a nearly identical process to create the storage object. // The derived class is responsible for writing a CreateStorageObject() // function that converts an object_type integer to a created storage // object. The derived class also has to read in the storage object // data. // int object_type = mpArchiveStorageObject->ReadChar(); ALStorage *storage_object = CreateStorageObject( name, object_type ); delete[] name; // Don't need it any more name = 0; if ( storage_object ) storage_object->ReadStorageObjectData( mpArchiveStorageObject ); else { char *temp = mpArchiveStorageObject->ReadString(); if ( temp ) delete[] temp; return mStatus.SetError( AL_CANT_CREATE_STORAGE_OBJECT, "Failure creating storage object for object %s", name ); } // // The rest of the stuff in the entry is pretty straightforward. // mpArchiveStorageObject->ReadPortableLong( storage_object->mlSize ); ALEntry *job = new ALEntry( list, storage_object, engine ); mpArchiveStorageObject->ReadPortableLong( job->mlCompressedSize ); mpArchiveStorageObject->ReadPortableLong( job->mlCrc32 ); mpArchiveStorageObject->ReadPortableLong( job->mlCompressedObjectPosition ); char *comment = mpArchiveStorageObject->ReadString(); job->SetComment( comment ); if ( comment ) delete[] comment; long unix_time; mpArchiveStorageObject->ReadPortableLong( unix_time ); storage_object->mTimeDate.SetTimeDate( unix_time ); short int packed_attributes; mpArchiveStorageObject->ReadPortableShort( packed_attributes ); storage_object->mAttributes.SetFromPackedAttributes( packed_attributes ); } list.mrMonitor.ArchiveOperation( AL_END_DIRECTORY_READ, this, 0 ); return mStatus; } // // int ALArchiveBase::WriteArchiveData() // // ARGUMENTS: // // None. // // RETURNS // // AL_SUCCESS if everything writes out okay, or < AL_SUCCESS for trouble. // // DESCRIPTION // // Derived classes can write out customized archive data, for whatever // reasons they deem necessary. Our base class has nothing that it // needs to save, so it just writes out a zero length string, which takes // two bytes to save. Instead of using WriteString like I ought to, for // some reason I write the 0 out directly. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::WriteArchiveData() { return mpArchiveStorageObject->WritePortableShort( 0 ); } // // int ALArchiveBase::ReadArchiveData() // // ARGUMENTS: // // None. // // RETURNS // // AL_SUCCESS if things went well, < AL_SUCCESS it things go sour. // // DESCRIPTION // // The base class doesn't store anything in the archive specific // data area. That means that when I am reading the archive specific // data in, I should see a zero length string, which is the same thing // as a single short of value 0. I read it in and verify it here. // // Note that derived classes are free to override this function, but // nothing we ship with ArchiveLib does so. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::ReadArchiveData() { short temp; mpArchiveStorageObject->ReadPortableShort( temp ); AL_ASSERT( temp == 0, "ReadArchiveData(): archive data != 0" ); return mpArchiveStorageObject->mStatus; } // // int ALArchiveBase::Delete( ALEntryList &list, // ALArchiveBase &destination ) // // ARGUMENTS: // // list : A list of the objects to delete from the archive. // // destination : The destination archive, which is the result after // deleting all the objects from this. // // RETURNS // // AL_SUCCESS if things went well, < AL_SUCCESS otherwise. // // DESCRIPTION // // Delete is really more like copy. It doesn't actually delete objects // out of an existing archive. Instead it deletes by excluding the // specified objects from a copy command, copying only those objects // that aren't in the delete list. The resulting archive looks as if // it is one that has had objects deleted from it. // // After deleting, we do some renaming to make it look like the delete // operation did what you really expected. As a result, the original // archive (this) has been renamed to a backup, and the new archive // now has the original name of this. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALArchiveBase::Delete( ALEntryList AL_DLL_FAR &list, ALArchiveBase AL_DLL_FAR &destination ) { destination.SetComment( mszComment ); list.ToggleMarks(); destination.Create( *this, list ); list.ToggleMarks(); ALName temp = mpArchiveStorageObject->mName; mpArchiveStorageObject->RenameToBackup(); destination.mpArchiveStorageObject->Rename( (const char*)temp ); if ( destination.mStatus < 0 ) return mStatus = destination.mStatus; return mStatus; } // // int ALArchiveBase::FillListBox( HWND hDlg, int list_box = -1 ) // // ARGUMENTS: // // hDlg : The handle of the dialog box that has a list box in it. // If the value of list_box is set to -1, it means that // hDlg doesn't refer to a dialog box, instead it refers // to the actual list box itself. // // list_box : This is set to the id of the list box control found in // the hDlg dialog box. If this value is set to -1, it // means the hDlg parameter is the handle of the list box. // // RETURNS // // The count of marked items stuffed into the list box. // // DESCRIPTION // // This is a quicky useful function to read the names of all the // storage objects out of this, then stuffing them all into a list // box. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // #if defined( AL_WINDOWS_GUI ) int AL_PROTO ALArchiveBase::FillListBox( HWND hDlg, int list_box /* = -1 */ ) { ALEntryList list; HWND window; ReadDirectory( list ); if ( list_box != -1 ) window = GetDlgItem( hDlg, (short int) list_box ); else window = hDlg; int count; if ( ( count = list.FillListBox( window ) ) == 0 ) { if ( mStatus < 0 ) { SendMessage( window, LB_RESETCONTENT, 0, 0 ); SendMessage( window, LB_ADDSTRING, 0, (LPARAM)( (LPSTR) "Error!" ) ); } } return count; } #endif