commands/fzip/fzipup.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 26 Aug 2010 00:49:35 +0100
changeset 45 534b01198c2d
parent 0 7f656887cf89
permissions -rw-r--r--
Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase. Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.

// fzipup.cpp
// 
// Copyright (c) 2008 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
// 
// Initial Contributors:
// Accenture - Initial contribution
//

#include <ezstream.h>
#include <ezcompressor.h>
#include <ezdecompressor.h>
#include <ezfilebuffer.h>
#include <f32file.h>
#include "fzipup.h"

const TInt KDefaultDiskNumber = 0;
const TInt CZipEntry::iOffset = _FOFF(CZipEntry, iLink);
const TUint16 KFZipVersion = 0x0014;
TInt KWindowBits = -CEZCompressor::EMaxWBits; // negative value is an undocumented feature of our zlib implementation, ensuring we do not include the zlib header in the compressed data (which we must do for zip-file format support)
const TInt KCompressedSizeRelativePosition = 18; // iCompressedSize is 18 bytes in from the current entry's LFH

//
// CBufferManager
//
CBufferManager *CBufferManager::NewLC(RFileReadStream& aInput, RFileWriteStream& aOutput, TInt aInputStreamBytes, TInt aBufferSize)
	{
	CBufferManager *bm = new (ELeave) CBufferManager(aInput, aOutput, aInputStreamBytes, aBufferSize);
	CleanupStack::PushL(bm);
	bm->ConstructL();
	return bm;
	}

CBufferManager::CBufferManager(RFileReadStream& aInput, RFileWriteStream& aOutput, TInt aInputStreamBytes, TInt aBufferSize)
:iInput(aInput), iOutput(aOutput), iInputStreamBytes(aInputStreamBytes), iBufferSize(aBufferSize), iInputDescriptor(NULL,0), iOutputDescriptor(NULL,0)
	{
	}

CBufferManager::~CBufferManager()
	{
	delete[] iInputBuffer;
	delete[] iOutputBuffer;
	}

void CBufferManager::ConstructL()
	{
	if (iInputStreamBytes < iBufferSize)
		iBufferSize = iInputStreamBytes;
	if (iBufferSize <=0)
		{
		User::Leave(KErrUnderflow);
		}
	
	iInputBuffer = new (ELeave) TUint8[iBufferSize];
	iOutputBuffer = new (ELeave) TUint8[iBufferSize];
	
	iInputDescriptor.Set(iInputBuffer,0,iBufferSize);
	iOutputDescriptor.Set(iOutputBuffer,0,iBufferSize);
	
	iReadLength = iBufferSize;
	}
	
void CBufferManager::InitializeL(CEZZStream &aZStream)
	{
	ReadInputL();
	aZStream.SetInput(iInputDescriptor);
	aZStream.SetOutput(iOutputDescriptor);
	}

void CBufferManager::NeedInputL(CEZZStream &aZStream)
	{
	ReadInputL();
	aZStream.SetInput(iInputDescriptor);
	}

void CBufferManager::NeedOutputL(CEZZStream &aZStream)
	{
	WriteOutputL(aZStream);
	aZStream.SetOutput(iOutputDescriptor);
	}

void CBufferManager::FinalizeL(CEZZStream &aZStream)
	{
	WriteOutputL(aZStream);
	}

void CBufferManager::ReadInputL()
	{
	iInput.ReadL(iInputDescriptor, iReadLength);
	iInputStreamBytes -= iReadLength;
	if (iInputStreamBytes < iBufferSize)
		{
		iReadLength = iInputStreamBytes;
		}
	if (iReadLength < 0)
		{
		User::Leave(KErrOverflow);
		}
	}

void CBufferManager::WriteOutputL(CEZZStream& aZStream)
	{
	TPtrC8 od = aZStream.OutputDescriptor();
	iOutput.WriteL(od);
	iBytesWritten += od.Size();
	}

//
// CZipEntry
// 
CZipEntry* CZipEntry::NewLC(RFs& aFs, const TDesC& aFilename, const TInt aUid)
	{
	CZipEntry* self = new (ELeave) CZipEntry(aFs, aUid);
	CleanupStack::PushL(self);
	self->ConstructL(aFilename);
	return self;
	}

CZipEntry::CZipEntry(RFs& aFs, const TInt aUid):
iFs(aFs), iUid(aUid)
	{
	iLFH.iCRC32 = crc32(iLFH.iCRC32, NULL, 0);
	}

CZipEntry::~CZipEntry()
	{
	if (iFileData)
		delete iFileData;
	if (iInput.SubSessionHandle() > 0)
		iInput.Close();
	}
	
void CZipEntry::ConstructL(const TDesC& aFilename)
	{
	User::LeaveIfError(iInput.Open(iFs, aFilename, EFileRead | EFileShareReadersOnly));

	// ensure aFileName is stored in accordance with zip file-format specification
	TFileName2 file(aFilename);
	if (file.HasDriveLetter())
		{
		file.Delete(0, 3); // remove '<drive>:\'
		}
	if (file.HasLeadingSlash())
		{
		file.Delete(0, 1); // remove '\'
		}
	iAsciiName.Copy(file);
	TUint8* ptr = const_cast<TUint8*> (iAsciiName.Ptr());
	TInt count = 0;
	do
		{
		if (*ptr == '\\')
			{
			*ptr = '/';
			}
		ptr++;
		} while (count++ < iAsciiName.Length());
	
	// config. local file header
	iLFH.iSignature = KLocalHeaderSignature;
	iLFH.iVersionNeeded = KFZipVersion;
	iLFH.iFlags = 0x0002; // best (-exx/-ex) compression was used
	iLFH.iCompressionMethod = EDeflated;
	TTime time;
	User::LeaveIfError(iInput.Modified(time));
	TDateTime td = time.DateTime();
	TUint16 param1 = td.Year() - 1980;
	TUint16 param2 = td.Month() + 1;
	TUint16 param3 = td.Day() + 1;
	iLFH.iLastModifiedFileDate = (param1 << 9) | (param2 << 5) | param3;
	param1 = td.Hour() + 1;
	param2 = td.Minute() + 1;
	param3 = td.Second() + 1;
	iLFH.iLastModifiedFileTime = (param1 << 11) | (param2 << 5) | (param3 >> 1);
	TInt uSize = 0;
	User::LeaveIfError(iInput.Size(uSize));
	if (uSize <= 0)
		{
		User::Leave(KErrAbort);
		}
	iLFH.iUncompressedSize = uSize;
	iLFH.iCompressedSize = uSize; // note: to be adjusted later once the compression is performed
	CalcCRCL();
	iLFH.iFileNameLength = iAsciiName.Size();
	iLFH.iExtraFieldLength = 0;
	
	// config. file header
	iFH.iSignature = KCentralDirectoryHeaderSignature;
	iFH.iMadeBy = KFZipVersion;
	iFH.iRequired = KFZipVersion;
	iFH.iFlags = iLFH.iFlags;
	iFH.iCompressionMethod = iLFH.iCompressionMethod;
	iFH.iLastModifiedFileTime = iLFH.iLastModifiedFileTime;
	iFH.iLastModifiedFileDate = iLFH.iLastModifiedFileDate;
	iFH.iCRC32 = iLFH.iCRC32;
	iFH.iCompressedSize = iLFH.iCompressedSize;	// note: to be adjusted later once the compression is performed
	iFH.iUncompressedSize = iLFH.iUncompressedSize;
	iFH.iFileNameLength = iLFH.iFileNameLength;
	iFH.iExtraFieldLength = iLFH.iExtraFieldLength;
	iFH.iFileCommentLength = 0x0000;
	iFH.iDiskNumberStart = KDefaultDiskNumber;
	iFH.iInternalFileAttributes = 0x0001;
	iFH.iExternalFileAttributes = 0x00000020;	// hardcoded to 'Archive'
	iFH.iLocalHeaderOffset = 0x00000000; // to be adjusted later (if necessary)
	}

void CZipEntry::SetOffset(TUint32 aOffset)
	{
	iFH.iLocalHeaderOffset = aOffset;
	}

TUint32 CZipEntry::ReturnOffset()
	{
	TUint32 result = iFH.iLocalHeaderOffset; // start with the offset of the LFH
	result += KLocalHeaderFixedLength; // add the size of the LFH itself
	result += iAsciiName.Size(); // add the size of the filename
	result += iLFH.iCompressedSize; // add any compressed data
	return result;
	}

TUint32 CZipEntry::FileHeaderSize()
	{
	TUint32 result = KCentralDirectoryHeaderFixedLength;
	result += iAsciiName.Size();
	return result;
	}

//
// CZipEntry::CalcCRCL
// mem. efficient means of calculating the crc on uncompressed data
// we require a maximum of KDefaultZipBufferLength heap space regardless size of data
//
void CZipEntry::CalcCRCL()
	{
	ASSERT(iInput.SubSessionHandle() > 0);
	TInt bytesRead = 0;
	TInt length = KDefaultZipBufferLength; // 32Kb
	if (iLFH.iUncompressedSize < KDefaultZipBufferLength)
		{
		length = iLFH.iUncompressedSize;
		}
	
	iLFH.iCRC32 = crc32(0, NULL, 0);
	HBufC8* crc = HBufC8::NewLC(length);
	TPtr8 crcPtr = crc->Des();
	User::LeaveIfError(iInput.Seek(ESeekStart, bytesRead));
	do
		{
		User::LeaveIfError(iInput.Read(crcPtr, length));
		iLFH.iCRC32 = crc32(iLFH.iCRC32, crcPtr.Ptr(), length);
		bytesRead += length;
		if ((iLFH.iUncompressedSize - bytesRead) < KDefaultZipBufferLength)
			{
			length = iLFH.iUncompressedSize - bytesRead;
			}
		} while (length > 0);
	CleanupStack::PopAndDestroy(1); // crc	
	}

//
// CZipItUp
// 
CZipItUp* CZipItUp::NewLC(RFs& aFs, TDesC& aArchive)
	{
	CZipItUp* self = new (ELeave) CZipItUp(aFs, aArchive);
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}		

CZipItUp::CZipItUp(RFs& aFs, TDesC& aArchive):
iFs(aFs), iFileName(aArchive), iZipMemberLinkedList(CZipEntry::iOffset), iZipMemberLinkedListIter(iZipMemberLinkedList)
	{
	}

CZipItUp::~CZipItUp()
	{
	if (iFile.SubSessionHandle() > 0)
		{
		iFile.Close();
		}
	while (!iZipMemberLinkedList.IsEmpty())
		{
		CZipEntry* entry = iZipMemberLinkedList.First();
		iZipMemberLinkedList.Remove(*entry);
		delete entry;	
		}
	iZipMemberLinkedList.Reset();
	}
	
void CZipItUp::ConstructL()
	{
	User::LeaveIfError(iFile.Create(iFs, iFileName, EFileStream | EFileShareExclusive));
	}
	
//
// CZipItUp::AddFileL
//
void CZipItUp::AddFileL(const TDesC& aFile)
	{
	if (!DuplicateEntryL(aFile))
		{
		// spawn a new zip entry, compress the file, add it to the list of zip file contained in the archive
		CZipEntry* zipEntry = CZipEntry::NewLC(iFs, aFile, ++iEntryUid);
		AddEntryL(*zipEntry);
		CleanupStack::Pop(zipEntry);
		}
	}

//
// CZipItUp::DuplicateEntryL
// run a check against the zip archive's current entries (if any!) for duplicate files
// return ETrue if a duplicate is found
//
TBool CZipItUp::DuplicateEntryL(const TDesC& aFile)
	{
	if (iZipMemberLinkedList.IsEmpty())
		{
		return EFalse;
		}
	
	// turn aFile into a zip-file format filename
	TFileName2 file(aFile);
	if (file.HasDriveLetter())
		{
		file.Delete(0, 3); // remove '<drive>:\'
		}
	if (file.HasLeadingSlash())
		{
		file.Delete(0, 1); // remove '\'
		}
	TBuf8<KMaxFileName> ascii;
	ascii.Copy(file);
	TUint8* ptr = const_cast<TUint8*> (ascii.Ptr());
	TInt count = 0;
	do
		{
		if (*ptr == '\\')
			{
			*ptr = '/';
			}
		ptr++;
		} while (count++ < ascii.Length());
	
	// iterate through zip archive entries looking for a duplicate 
	iZipMemberLinkedListIter.SetToFirst();
	CZipEntry* entry = iZipMemberLinkedListIter++;
	while (entry != NULL)
		{
		if (entry->iAsciiName == ascii)
			{
			return ETrue;
			}
		entry = iZipMemberLinkedListIter++;
		}
	return EFalse;
	}

//
// CZipItUp::AddEntryL
// Add an entry to the zip archive.
//
void CZipItUp::AddEntryL(CZipEntry& aEntry)
	{
	// append the new entry to the queue
	if (!iZipMemberLinkedList.IsEmpty())
		{
		// prior entries in the list therefore need to adjust this entry's local file header offset
		CZipEntry* lastEntry = iZipMemberLinkedList.Last();
		User::LeaveIfNull(lastEntry);
		}
	iZipMemberLinkedList.AddLast(aEntry);
	}

//
// CZipItUp::CreateZipL
// Creates the actual file, including all the previously compressed zip files in the archive
//
void CZipItUp::CreateZipL()
	{
	ASSERT(iFile.SubSessionHandle() > 0);
	if (iZipMemberLinkedList.IsEmpty())
		{
		User::Leave(KErrGeneral);
		}
	RFileWriteStream stream(iFile);
	stream.PushL();
	
	// initialise central directory header
	iCDT.iSignature = CZipArchive::KCentralDirectorySignature;
	iCDT.iDiskNumber = KDefaultDiskNumber;
	iCDT.iStartDiskNumber = KDefaultDiskNumber;
	iCDT.iLocalEntryCount = 0;
	iCDT.iTotalEntryCount = 0;
	iCDT.iSize = 0;
	iCDT.iCommentLength = 0;
	
	// iterate through each zip entry, appending local file header (LFH), file data (FD) to file
	iZipMemberLinkedListIter.SetToFirst();
	CZipEntry* entry = iZipMemberLinkedListIter++;
	CZipEntry* prior = NULL;
	User::LeaveIfNull(entry);
	do
		{
		if (prior)
			{
			// adjust file header local offset if necessary
			entry->SetOffset(prior->ReturnOffset());
			}

		// insert entry's Local File Header
		AppendLocalFileHeaderL(stream, *entry);
		
		// insert entry's File Data
		AppendCompressedDataL(stream, *entry);
		
		// adjust cdt parameters
		iCDT.iLocalEntryCount++;
		iCDT.iTotalEntryCount++;
		iCDT.iSize += entry->FileHeaderSize();
		
		// move onto the next entry
		prior = entry;
		entry = iZipMemberLinkedListIter++;
		} while (entry != NULL);
		
	// adjust cdt offset parameter
	iCDT.iOffset = iZipMemberLinkedList.Last()->ReturnOffset();
	
	// iterate through each zip entry, appending the central directory File Headers to file (these must go after LFH, FD entries)
	iZipMemberLinkedListIter.SetToFirst();
	entry = iZipMemberLinkedListIter++;
	
	do
		{
		// central directory File Header
		AppendCentralDirectoryFileHeaderL(stream, *entry);
		
		// move onto the next entry
		entry = iZipMemberLinkedListIter++;
		} while (entry != NULL);
	
	// append the central directory trailer
	AppendCentralDirectoryTrailerL(stream);	
	
	// commit the transaction
	stream.CommitL();
	stream.Pop();
	stream.Close();
	
	// go back through & adjust compressed size values for each zip entry post-mortem
	ASSERT(iFile.SubSessionHandle() == 0);
	User::LeaveIfError(iFile.Open(iFs, iFileName, EFileWrite | EFileShareExclusive));
	TInt pos = 0;
	const TInt remainder = 8; // CZipArchive::KLocalHeaderFixedLength - KCompressedSizeRelativePosition
	User::LeaveIfError(iFile.Seek(ESeekStart, pos));
	iZipMemberLinkedListIter.SetToFirst();
	entry = iZipMemberLinkedListIter++;
	do
		{
		pos += KCompressedSizeRelativePosition; // step to iCompressedSize location in the current entry's LFH
		TUint32 cs = entry->iLFH.iCompressedSize;
		TUint32 KMyByte = 0x000000FF;
		TInt bytecount = 0;
		TBuf8<8> compSize;
		do
			{
			TUint32 csChar((cs & KMyByte));
			compSize.Append(csChar);
			cs >>= 8;
			} while (++bytecount < 4); // zip-file format allows us 4 bytes only for the compressed size value
		User::LeaveIfError(iFile.Write(pos, compSize));
		pos += bytecount; // the 4 bytes we just wrote
		
		// step over remaing bytes to the beginning of the next entry's LFH
		pos += remainder; 
		pos += entry->iAsciiName.Size();
		pos += entry->iLFH.iCompressedSize;
		
		// we're now positioned at the next entry's LFH
		entry = iZipMemberLinkedListIter++;
		} while (entry != NULL);
	}

void CZipItUp::AppendLocalFileHeaderL(RFileWriteStream& aStream, CZipEntry& aZipEntry)
	{
	aStream.WriteUint32L(aZipEntry.iLFH.iSignature);
	aStream.WriteUint16L(aZipEntry.iLFH.iVersionNeeded);
	aStream.WriteUint16L(aZipEntry.iLFH.iFlags);
	aStream.WriteUint16L(aZipEntry.iLFH.iCompressionMethod);
	aStream.WriteUint16L(aZipEntry.iLFH.iLastModifiedFileTime);
	aStream.WriteUint16L(aZipEntry.iLFH.iLastModifiedFileDate);
	aStream.WriteUint32L(aZipEntry.iLFH.iCRC32);
	aStream.WriteUint32L(aZipEntry.iLFH.iCompressedSize);
	aStream.WriteUint32L(aZipEntry.iLFH.iUncompressedSize);
	aStream.WriteUint16L(aZipEntry.iLFH.iFileNameLength);
	aStream.WriteUint16L(aZipEntry.iLFH.iExtraFieldLength);
	aStream.WriteL(aZipEntry.iAsciiName);
	}

void CZipItUp::AppendCompressedDataL(RFileWriteStream& aStream, CZipEntry& aZipEntry)
	{
	// compress data & stream it to the zip archive (aStream)
	RFileReadStream inStream;
	inStream.Attach(aZipEntry.iInput); // note takes ownership of iInput subsession handle
	CBufferManager* fb = CBufferManager::NewLC(inStream, aStream, aZipEntry.iLFH.iUncompressedSize, KDefaultZipBufferLength);
	CEZCompressor* compressor = CEZCompressor::NewLC(*fb, CEZCompressor::EBestCompression, KWindowBits, CEZCompressor::EDefMemLevel, CEZCompressor::EDefaultStrategy);
	while (compressor->DeflateL()){/* do nothing */}
	
	// update the entry's LocalFileHeader and FileHeader
	aZipEntry.iLFH.iCompressedSize = fb->TotalBytesOut();
	aZipEntry.iFH.iCompressedSize = aZipEntry.iLFH.iCompressedSize;
	
	// cleanup
	CleanupStack::PopAndDestroy(2, fb); // compressor, fb
	inStream.Close();
	}

void CZipItUp::AppendCentralDirectoryFileHeaderL(RFileWriteStream& aStream, CZipEntry& aEntry)
	{
	aStream.WriteUint32L(aEntry.iFH.iSignature);
	aStream.WriteUint16L(aEntry.iFH.iMadeBy);
	aStream.WriteUint16L(aEntry.iFH.iRequired);
	aStream.WriteUint16L(aEntry.iFH.iFlags);
	aStream.WriteUint16L(aEntry.iFH.iCompressionMethod);
	aStream.WriteUint16L(aEntry.iFH.iLastModifiedFileTime);
	aStream.WriteUint16L(aEntry.iFH.iLastModifiedFileDate);
	aStream.WriteUint32L(aEntry.iFH.iCRC32);
	aStream.WriteUint32L(aEntry.iFH.iCompressedSize);
	aStream.WriteUint32L(aEntry.iFH.iUncompressedSize);
	aStream.WriteUint16L(aEntry.iFH.iFileNameLength);
	aStream.WriteUint16L(aEntry.iFH.iExtraFieldLength);
	aStream.WriteUint16L(aEntry.iFH.iFileCommentLength);
	aStream.WriteUint16L(aEntry.iFH.iDiskNumberStart);
	aStream.WriteUint16L(aEntry.iFH.iInternalFileAttributes);
	aStream.WriteUint32L(aEntry.iFH.iExternalFileAttributes);
	aStream.WriteUint32L(aEntry.iFH.iLocalHeaderOffset);
	aStream.WriteL(aEntry.iAsciiName);
	}

void CZipItUp::AppendCentralDirectoryTrailerL(RFileWriteStream& aStream)
	{
	aStream.WriteUint32L(iCDT.iSignature);
	aStream.WriteUint16L(iCDT.iDiskNumber);
	aStream.WriteUint16L(iCDT.iStartDiskNumber);
	aStream.WriteUint16L(iCDT.iLocalEntryCount);
	aStream.WriteUint16L(iCDT.iTotalEntryCount);
	aStream.WriteUint32L(iCDT.iSize);
	aStream.WriteUint32L(iCDT.iOffset);
	aStream.WriteUint16L(iCDT.iCommentLength);	
	}