// // ARCENTRY.CPP // // Source file for ArchiveLib 1.0 // // Copyright (c) Greenleaf Software, Inc. 1994 // All Rights Reserved // // CONTENTS // // ALEntry::operator new() // ALEntryList::operator new() // ALEntry::ALEntry() // ALEntry::~ALEntry() // ALEntry::Duplicate() // ALEntry::InsertBefore() // ALEntry::SetComment() // ALEntry::CompressionRatio() // ALEntryList::ALEntryList() // ALEntryList::~ALEntryList() // ALEntryList::SetMarkState() // ALEntryList::ToggleMarks() // ALEntry::GetNextEntry() // ALEntryList::UnmarkDuplicates() // ALEntryList::DeleteUnmarked() // ALEntryList::FillListBox() //Windows GUI only // ALEntryList::SetMarksFromListBox() //Windows GUI only // // DESCRIPTION // // This file contains the source code for two class, ALEntry and // ALEntryList. They are so tightly bound together that it made // sense to go ahead and stick them both in the same file. // // Class ALEntry describes the state of an object that is in an archive. // It contains a pointer to a storage object and a compression engine, // which define what goes in the archive and how it is put there. It // also defines how to extract it. The ALEntry object also contains // miscellaneous items that go with the object in the archive, such // as its time/date stamp, its CRC, and its comment. // // You have to create an ALEntry *before* you put an object into an // archive. The archiving class member function figure out what you want // to do by looking at objects of the ALEntry. You also have to read // the contents of the archive into a list of ALEntry objects before you // can extract anything. // // ALEntryList is simply a class that is used to keep a linked list of // ALEntry objects together. ALEntryList objects are passed to the high // level ALArchiveBase functions for common operations, such as Create() // Extract(), and so on. You get an ALEntryList back when you read the // directory of an archive. The ALEntryList object tells you everything // there is to know about the object stored in the archive. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // // #include "arclib.h" #include #pragma hdrstop // // void * ALEntry::operator new( size_t size ) // // ARGUMENTS: // // size : The number of bytes needed to create a new ALEntry 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 create 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 schemed, 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 ALEntry::operator new( size_t size ) { return ::new char[ size ]; } #endif // // void * ALEntryList::operator new( size_t size ) // // ARGUMENTS: // // size : The number of bytes that are going to need to be allocated // to create a new ALEntryList object. // // RETURNS // // A pointer to the newly allocated storage area. A 0 is returned if no // storage could be found. // // DESCRIPTION // // Look at the explanation for ALEntry::operator new(), directly above // this guy. The description is identical. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // #if defined( AL_BUILDING_DLL ) void AL_DLL_FAR * AL_PROTO ALEntryList::operator new( size_t size ) { return ::new char[ size ]; } #endif // // ALEntry::ALEntry( ALEntryList &list, // ALStorage *object, // ALCompressionEngine *engine ) // // ARGUMENTS: // // list : A reference to the list the ALEntry object is going to // be linked into. ALEntry objects aren't allowed to exist // without being in a list. // // object : A pointer to the storage object that is attached to this // entry. Remember, this is an unopened storage object, // so it is not consuming very much space. It is okay // to have a zillion or so of these just lying around. // Don't forget that the ALEntry dtor is going to destroy // this guy for you, don't you dare try it!. // // engine : A pointer to the compression engine that is going to // be used to create/insert/extract the storage object // to/from the archive. Just like with the compression // engine, it is a low cost object, and you can keep lots // of them on hand. This engine will be destroyed in the // ALEntry dtor, so be sure to give up any claim you might // have on this guy. // // RETURNS // // Nothing, this is a ctor. // // DESCRIPTION // // This ctor creates a new ALEntry object. You can do this by hand, but // frequently you will ask ArchiveLib to create ALEntry objects for you, // maybe by pulling them out of a list box, or reading them in from and // archive. Note that ALEntry objects aren't allowed to ever exist // outside a list, each entry absolutely has to appear in a list. // // dtor issues relating to the ALEntry object are very important. Since // ALEntry objects always are part of a list, it made sense for the // ALEntryList destructor to clean up all the entries in its list. So // even though you might have created this ALEntry object, you don't get to // delete it, that will be done for you. // // Also, the storage object and compression engine in the ALEntry object // are going to be automatically destroyed by the ALEntry dtor. Don't // even think about trying it yourself! // // You can think of an ALEntryList as a directory of an archive, and each // ALEntry object in the list is a single entry in that directory. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // AL_PROTO ALEntry::ALEntry( ALEntryList &list, ALStorage *object, ALCompressionEngine *engine ) : mrList( list ) // Initialize our own pointer to the list we will // be a member of. { mpNextItem = this; mpPreviousItem = this; mpStorageObject = object; mpCompressionEngine = engine; mlCompressedSize = -1; mlCompressedObjectPosition = -1; miMark = 1; //Always construct with the mark turned on mszComment = 0; // // I check for the object member to be non-zero because of a clunky design // choice I made a while back. Each ALEntryList has an ALEntry member that // points to the first and last members of the list. I could have (and // probably should have) made the root of the list just be a pair of pointers, // instead of a dummy ALEntry. Anyway, I can tell that dummy entry apart // from the valid entries by virtue of the fact that it has a null // pointer in its object pointer. // // So anyway, when I create this dummy object, I don't want to try to add // it to the list, because by definition it is already in the list. So // I do a check before adding any ALEntry to the list. // if ( object ) InsertBefore( *list.mpListHead ); } // // ALEntry::~ALEntry() // // ARGUMENTS: // // Nothing. // // RETURNS // // Nothing, this is a destructor. // // DESCRIPTION // // This destructor should normally be called by the ALEntryList dtor. The // list that owns an entry will always try to delete it when the list // is deleted. // // The ALEntry object tries to delete three dynamically allocated objects // that it has control over: the storage object, the compression engine, // and the comment. In each case it won't do it if the object pointer // is 0. This provides a convenient mechanism for you to steal a storage // object from an ALEntry. All you have to do is take the pointer, and // then sent ALEntry::mpStorageObject to 0. This is an especially useful // thing to do for ALMemory objects. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // AL_PROTO ALEntry::~ALEntry() { AL_ASSERT( GoodTag(), "~ALEntry: Attempting to delete invalid object" ); if ( mszComment ) delete[] mszComment; if ( mpStorageObject != 0 ) delete mpStorageObject; if ( mpCompressionEngine != 0 ) delete mpCompressionEngine; AL_ASSERT( mpNextItem != 0 ,"~ALEntry: next item is null" ); AL_ASSERT( mpPreviousItem != 0, "~ALEntry: previous item is null" ); ALEntry *next_job = mpNextItem; ALEntry *previous_job = mpPreviousItem; if ( next_job != this ) { next_job->mpPreviousItem = previous_job; previous_job->mpNextItem = next_job; } // // Note that I check the object twice, one at the start of the dtor, and // once again at the end. With all the linked list and dynamic deletion // being done here, it seems like it would be really easy to hose things // up if any mistakes were made. // AL_ASSERT( GoodTag(), "~ALEntry: Attempting to delete invalid object" ); } // // int ALEntry::Duplicate( ALEntryList &list ) // // ARGUMENTS: // // list : A list of ALEntry objects to scan. // // RETURNS // // 0 if the entry is not duplicated, 1 if it is. // // DESCRIPTION // // This function is used to scan a list of ALEntry objects to see if // any of them have the same name as this. Unmarked objects are ignored. // All the function does is zip through the ALEntryList, checking each // marked member for an ASCII match with the name of the storage object // pointed to by this. You can see that the case sensitivity of this // is observed when making the comparison. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntry::Duplicate( ALEntryList &list ) { char *name = mpStorageObject->mName; int case_sensitive = mpStorageObject->mName.mCase == AL_MIXED; ALEntry *job = list.GetFirstEntry(); while ( job ) { if ( job->GetMark() && job != this ) { if ( case_sensitive ) { if ( strcmp( name, job->mpStorageObject->mName ) == 0 ) return 1; } else { if ( stricmp( name, job->mpStorageObject->mName ) == 0 ) return 1; } } job = job->GetNextEntry(); } return 0; } // // PROTECTED FUNCTION // // void ALEntry::InsertBefore( ALEntry &job ) // // ARGUMENTS: // // job : A reference to another job in the target list. // // RETURNS // // Nothing. // // DESCRIPTION // // This function is used inside the ALEntryList class to add a new ALEntry // object to an ALEntryList. Since the list is a doubly linked list, the // code to do the job is pretty simple. It would have been a little more // complicated if I used a pair of pointers in the ALEntryList to start // the list, instead of a dummy ALEntry object. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // void AL_PROTO ALEntry::InsertBefore( ALEntry &job ) { mpNextItem = &job; mpPreviousItem = job.mpPreviousItem; (job.mpPreviousItem)->mpNextItem = this; job.mpPreviousItem = this; } // // int ALEntry::SetComment( const char *comment ) // // ARGUMENTS: // // comment : The new comment that is going to be associated with the // ALEntry object. // // RETURNS // // AL_SUCCESS if the new comment was set, < 0 if an error occurred. // // DESCRIPTION // // Before adding an object to an archive, you may want to change or set // its comment. You do so by calling this function before performing any // operation that will write the directory, such as Create() or // WriteDirectory(). It has to dynamically allocate the space in the // ALEntry object in order to store the new comment. This is good for // you, because it means you don't have to worry about who owns the comment // you just passed in. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntry::SetComment( const char AL_DLL_FAR *comment ) { if ( mszComment ) delete[] mszComment; if ( comment ) { mszComment = new char[ strlen( comment ) + 1 ]; if ( mszComment ) strcpy( mszComment, comment ); else return mrList.mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Failed to allocate memory when " "adding comment to storage object %s", (char *) mpStorageObject->mName ); } else mszComment = 0; return AL_SUCCESS; } // // int ALEntry::CompressionRatio() // // ARGUMENTS: // // None. // // RETURNS // // The integer representing the compression ratio. The ration is a number // from 0 to 100 (or maybe more) with 0 being perfect compression. // // It is possible to get a -1 back from this routine if the compression // ratio is not presently known. This will be the case if you have // not created the archive yet, or have a new object that hasn't been // inserted yet. // // DESCRIPTION // // This calculates and returns the compression ratio. We don't store the // ratio in ALEntry, because it is so darned easy to calculate when // we need it. However, there are going to be times when we don't have // it. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntry::CompressionRatio() { long uncompressed_size = mpStorageObject->GetSize(); if ( uncompressed_size <= 0 ) return -1; if ( mlCompressedSize <= 0 ) return -1; return (int) ( 100 * mlCompressedSize / uncompressed_size ); } // // ALEntryList::ALEntryList( ALMonitor * monitor = 0 ) // // ARGUMENTS: // // monitor : A pointer to a monitor that will be used whenever we are // processing objects in the list. If no argument is supplied, // the default argument value of 0 is used. When the ctor sees // that the value of the monitor pointer is 0, it assigns the // default monitor instead. // // RETURNS // // No returns from constructors. // // DESCRIPTION // // Constructing an ALEntryList object doesn't take much work. I have to // initialize two data members. The first is the pointer to the monitor // that will be used when processing objects in the list. The second is // the root of the linked list, which is a dummy ALEntry object. Note // that the root is created as a dummy by setting the storage object pointer // to 0. // // The default monitor is defined below. If you don't specify a real // monitor, you get the default, which is a do nothing function. Everyone // can share one instance of the default monitor, because it doesn't have // any data members to be concerned about. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // ALMonitor ALDefaultMonitor( AL_MONITOR_OBJECTS ); AL_PROTO ALEntryList::ALEntryList( ALMonitor AL_DLL_FAR * monitor /* = 0 */ ) : mrMonitor( monitor ? *monitor : ALDefaultMonitor ) { mpListHead = new ALEntry( *this, 0, 0 ); } // // ALEntryList::~ALEntryList() // // ARGUMENTS: // // None. // // RETURNS // // None, destructors don't get to return anything. // // DESCRIPTION // // The destructor for ALEntryList goes through the list and deletes every // ALEntry object it finds. Note that this also causes the ALEntry // object to destroy its storage object and compression engine. Once // the whole list is obliterated, the list head ALEntry object can be // safely deleted. Then the whole thing is done. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // AL_PROTO ALEntryList::~ALEntryList() { AL_ASSERT( GoodTag(), "~ALEntryList: attempting to delete invalid object" ); ALEntry *job = GetFirstEntry(); while ( job ) { ALEntry *next_job = job->GetNextEntry(); delete job; job = next_job; } if ( mpListHead ) delete mpListHead; AL_ASSERT( GoodTag(), "~ALEntryList: attempting to delete invalid object" ); } // PROTECTED FUNCTION // // int ALEntryList::SetMarkState( const char *name, // short int new_state ) // // ARGUMENTS: // // name : The object name, specifying which storage objects are // to have their state set. This name can include // wild card characters. Note that passing a null // pointer here will cause a match to *every* object name. // // new_state : The new state that the ALEntry mark should be set to. // // RETURNS // // A count of the number of ALEntry objects whose state was changed. // // DESCRIPTION // // This protected function is used internally to help out a couple of the // public functions. It rips through every entry of the list, checks to // see if storage object associate with the entry has a name that matches // the wildcard specification, and sets the mark if it does. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntryList::SetMarkState( const char AL_DLL_FAR *name, short int new_state ) { int count = 0; ALEntry *job = GetFirstEntry(); while ( job ) { if ( name ) { if ( job->mpStorageObject->mName.WildCardMatch( name ) ) { job->SetMarkState( new_state ); count++; } } else { job->SetMarkState( new_state ); count++; } job = job->GetNextEntry(); } return count; } // // int ALEntryList::ToggleMarks() // // ARGUMENTS: // // None. // // RETURNS // // A count of the number of entries whose mark was changed. // (Just the total number of entries.) // // DESCRIPTION // // This simple member function just goes through the entire list, // toggling the mark state of every entry. In other words, if the mark // was previously set, it will now be cleared, and vice versa. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntryList::ToggleMarks() { int count = 0; ALEntry *job = GetFirstEntry(); while ( job ) { job->SetMarkState( (short int) !job->GetMark() ); job = job->GetNextEntry(); count++; } return count; } // // ALEntry * ALEntry::GetNextEntry() // // ARGUMENTS: // // None. // // RETURNS // // A pointer to the next entry in the list. If the next entry is the // list head, it means we have reached the end of the list, and a value // of 0 is returned. // // DESCRIPTION // // This function is used to iterate through the list. Each entry has // a pointer to the next and previous entries, so this function is really // simple. The only complications comes from trying to detect the end of // the list, which is denoted by the list head instance of ALEntry. We // can tell it apart from all the legitimate entries by the fact that // its storage object is 0. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // ALEntry AL_DLL_FAR * AL_PROTO ALEntry::GetNextEntry() { ALEntry *next_entry = this->mpNextItem; // // The list head has the special case where both the compression engine // and storage object pointers are 0, and that makes the end of the list. // if ( mpNextItem->mpStorageObject == 0 ) return 0; else return next_entry; } // // ALEntry * ALEntryList::GetFirstEntry() // // ARGUMENTS: // // None. // // RETURNS // // A pointer to the first valid ALEntry object in the list, or 0 if there // are no entries. // // DESCRIPTION // // If you are going to iterate through the entire list, this function is // used to start you off. It gets the first entry in the list by call // GetNextEntry() for the list head. Don't worry about what happens if // the list is empty, the GetNextEntry() code figures that out with no // problem, and returns a 0. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // ALEntry AL_DLL_FAR * AL_PROTO ALEntryList::GetFirstEntry() { return mpListHead->GetNextEntry(); } // // void ALEntryList::UnmarkDuplicates( ALEntryList &list, // const char *error_message = 0 ) // // ARGUMENTS: // // list : The list that is going to be compared to this. // // error_message : Each entry in this that turns out to have a duplicate // entry in the list argument will not only be unmarked, // it will also have its error status set, if an error // message is provide. // // RETURNS // // Nothing. // // DESCRIPTION // // I think this function is a little confusing. At first blush, you would // probably expect this function to scan all the items in a single list, // and unmark any object that turn out to have duplicates elsewhere // in the list. Unfortunately, it doesn't work that way. // // Instead, this function goes through the list specified by this, and // checks to see if each entry in this appears in the list specified by // the list parameter. This means that we are working with two different // lists, which certainly offers plenty of chances to get confused. // // Anyway, each entry in this that turns out to have a duplicate gets its // mark cleared. If the calling program specifies and error message, // the entry also gets its mStatus error member set to flag this as an // error. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // void AL_PROTO ALEntryList::UnmarkDuplicates( ALEntryList &list, const char *error_message /* = 0 */ ) { ALEntry *job = GetFirstEntry(); while ( job ) { if ( job->GetMark() ) { if ( job->Duplicate( list ) ) { job->ClearMark(); if ( error_message && error_message[ 0 ] != '\0' ) job->mpStorageObject->mStatus.SetError( AL_DUPLICATE_ENTRY, error_message ); } } job = job->GetNextEntry(); } } // // int ALEntryList::DeleteUnmarked() // // ARGUMENTS: // // None. // // RETURNS // // The number of entries that are deleted. // // DESCRIPTION // // Sometimes you may have a list with a whole bunch of unmarked entries. // Those unmarked entries are just sitting there taking up space, so it // would be handle to be able to just delete them. That is what this // function does. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntryList::DeleteUnmarked() { ALEntry *job; int count = 0; job = GetFirstEntry(); while ( job ) { ALEntry *next_job = job->GetNextEntry(); if ( job->GetMark() == 0 ) { count++; delete job; } job = next_job; } return count; } // // in ALEntryList::FillListBox( HWND hDlg, int list_box = -1 ) // // ARGUMENTS: // // hDlg : The handle of the dialog box that contains the list box // control. If the list box is not a control in a dialog, // set the next parameter to -1, and just pass the handle // of the list box in this argument. // // list_box : The ID of the list box, if and only if the list box is // in a dialog box specified by by the hDlg argument. // // RETURNS // // The number of entries that were stuffed into the list box. // // DESCRIPTION // // This function is a handy helper when using the Windows GUI. It goes // through an ALEntryList, and finds all the marked entries. For every // marked entry, it stuffs the name of the storage object into the list box. // This means that if you are planning on letting the user select a list // of storage objects, you can initialize the list with just one // function call. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // #if defined( AL_WINDOWS_GUI ) int AL_PROTO ALEntryList::FillListBox( HWND hDlg, int list_box /* = -1 */ ) { HWND window; if ( list_box != -1 ) window = GetDlgItem( hDlg, (short int) list_box ); else window = hDlg; SendMessage( window, LB_RESETCONTENT, 0, 0 ); ALEntry *job = GetFirstEntry(); int count = 0; while ( job ) { if ( job->GetMark() ) { count++; SendMessage( window, LB_ADDSTRING, 0, (LPARAM)( (LPSTR) job->mpStorageObject->mName ) ); } job = job->GetNextEntry(); } if ( count == 0 ) SendMessage( window, LB_ADDSTRING, 0, (LPARAM)( (LPSTR) "" ) ); return count; } // // int ALEntryList::SetMarksFromListBox( HWND hDlg, int list_box = - 1 ) // // ARGUMENTS: // // hDlg : The handle of the dialog box that contains the list box. // If the list box control is standalone window, this parameter // can be its handle, if the list_box argument is set to -1. // // list_box : The ID of the list box, if and only if it is contained in // a dialog box whose handle is specified in the hDlg param. // // RETURNS // // A count of the number of items whose marks were set. // // DESCRIPTION // // This function is called after you have given a user the opportunity // to set and clear items in a multiselection list box. Once the user // has done so, you can call this function, which will go through the // list and set all the marks that have been set in the list box by the // user. Note that it will not clear the marks on any of the ALEntry // objects in the list, you might want to do that first. // // REVISION HISTORY // // May 23, 1994 1.0A : First release // int AL_PROTO ALEntryList::SetMarksFromListBox( HWND hDlg, int list_box /* = -1 */ ) { HWND window; if ( list_box != -1 ) window = GetDlgItem( hDlg, (short int) list_box ); else window = hDlg; WORD count = (WORD) SendMessage( window, LB_GETSELCOUNT, 0, 0L ); int *items = new int[ count ]; if ( items == 0 ) return mStatus.SetError( AL_CANT_ALLOCATE_MEMORY, "Memory allocation failure in SetMarksFromListBox()" ); #ifdef AL_FLAT_MODEL if ( count != (WORD) SendMessage( window, LB_GETSELITEMS, count, (LPARAM) ( items ) ) ) { #else if ( count != (WORD) SendMessage( window, LB_GETSELITEMS, count, (LPARAM) ( (int _far * ) items ) ) ) { #endif mStatus.SetError( AL_LOGIC_ERROR, "Logic error in SetMarksFromListBox()." "Mismatch in select count from list box." ); delete[] items; return AL_LOGIC_ERROR; } for ( WORD i = 0 ; i < count ; i++ ) { WORD length = (WORD) SendMessage( window, LB_GETTEXTLEN, (short int) items[ i ], 0L ); AL_ASSERT( length != (WORD) LB_ERR, "SetMarksFromListBox: LB_ERR returned from list box" ); if ( length > 0 ) { char *name = new char[ length + 1 ]; if ( name ) { if ( SendMessage( window, LB_GETTEXT, (short int) items[ i ], (LPARAM)( (LPSTR) name ) ) >= 0 ) SetMarks( name ); delete[] name; } } } delete[] items; return count; } #endif