commands/fzip/fzipup.cpp
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/fzip/fzipup.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,554 @@
+// 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);	
+	}