//
// ZipManipulator.cpp
//
// $Id: //poco/1.4/Zip/src/ZipManipulator.cpp#1 $
//
// Library: Zip
// Package: Manipulation
// Module:  ZipManipulator
//
// Copyright (c) 2007, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
// 
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//


#include "Poco/Zip/ZipManipulator.h"
#include "Poco/Zip/ZipException.h"
#include "Poco/Zip/ZipUtil.h"
#include "Poco/Zip/Add.h"
#include "Poco/Zip/Delete.h"
#include "Poco/Zip/Keep.h"
#include "Poco/Zip/Rename.h"
#include "Poco/Zip/Replace.h"
#include "Poco/Zip/Compress.h"
#include "Poco/Delegate.h"
#include "Poco/File.h"
#include "Poco/FileStream.h"


namespace Poco {
namespace Zip {


ZipManipulator::ZipManipulator(const std::string& zipFile, bool backupOriginalFile):
	_zipFile(zipFile),
	_backupOriginalFile(backupOriginalFile),
	_changes(),
	_in(0)
{
	Poco::FileInputStream in(zipFile);
	_in = new ZipArchive(in);
}


ZipManipulator::~ZipManipulator()
{
}


void ZipManipulator::deleteFile(const std::string& zipPath)
{
	const ZipLocalFileHeader& entry = getForChange(zipPath);
	addOperation(zipPath, new Delete(entry));
}


void ZipManipulator::replaceFile(const std::string& zipPath, const std::string& localPath)
{
	const ZipLocalFileHeader& entry = getForChange(zipPath);
	addOperation(zipPath, new Replace(entry, localPath));
}


void ZipManipulator::renameFile(const std::string& zipPath, const std::string& newZipPath)
{
	const ZipLocalFileHeader& entry = getForChange(zipPath);
	// checked later in Compress too but the earlier one gets the error the better
	std::string fn = ZipUtil::validZipEntryFileName(newZipPath); 
	addOperation(zipPath, new Rename(entry, fn));
}


void ZipManipulator::addFile(const std::string& zipPath, const std::string& localPath, ZipCommon::CompressionMethod cm, ZipCommon::CompressionLevel cl)
{
	addOperation(zipPath, new Add(zipPath, localPath, cm, cl));
}


ZipArchive ZipManipulator::commit()
{
	// write to a tmp file
	std::string outFile(_zipFile + ".tmp");
	ZipArchive retVal(compress(outFile));
	//renaming
	{
		Poco::File aFile(_zipFile);
		if (_backupOriginalFile)
		{
			Poco::File tmp(_zipFile+".bak");
			if (tmp.exists())
				tmp.remove();
			aFile.renameTo(_zipFile+".bak");
		}
		else
			aFile.remove();
	}

	{
		Poco::File resFile(outFile);
		Poco::File zipFile(_zipFile);
		if (zipFile.exists())
			zipFile.remove();
		resFile.renameTo(_zipFile);
	}
	return retVal;
}


const ZipLocalFileHeader& ZipManipulator::getForChange(const std::string& zipPath) const
{
	ZipArchive::FileHeaders::const_iterator it = _in->findHeader(zipPath);
	if (it == _in->headerEnd())
		throw ZipManipulationException("entry not found: " + zipPath);

	if (_changes.find(zipPath) != _changes.end())
		throw ZipManipulationException("A change request exists already for entry " + zipPath);

	return it->second;
}


void ZipManipulator::addOperation(const std::string& zipPath, ZipOperation::Ptr ptrOp)
{
	std::pair<Changes::iterator, bool> result = _changes.insert(std::make_pair(zipPath, ptrOp));
	if (!result.second)
		throw ZipManipulationException("A change request exists already for entry " + zipPath);
}


void ZipManipulator::onEDone(const void*, const ZipLocalFileHeader& hdr)
{
	EDone(this, hdr);
}


ZipArchive ZipManipulator::compress(const std::string& outFile)
{
	// write to a tmp file
	Poco::FileInputStream in(_zipFile);
	Poco::FileOutputStream out(outFile);
	Compress c(out, true);
	c.EDone += Poco::Delegate<ZipManipulator, const ZipLocalFileHeader>(this, &ZipManipulator::onEDone);
	
	ZipArchive::FileHeaders::const_iterator it = _in->headerBegin();
	for (; it != _in->headerEnd(); ++it)
	{
		Changes::iterator itC = _changes.find(it->first);
		if (itC != _changes.end())
		{
			itC->second->execute(c, in);
			_changes.erase(itC);
		}
		else
		{
			Keep k(it->second);
			k.execute(c, in);
		}
	}
	//Remaining files are add operations!
	Changes::iterator itC = _changes.begin();
	for (; itC != _changes.end(); ++itC)
	{
		itC->second->execute(c, in);
	}
	_changes.clear();
	c.EDone -= Poco::Delegate<ZipManipulator, const ZipLocalFileHeader>(this, &ZipManipulator::onEDone);
	in.close();
	return c.close();
}


} } // namespace Poco::Zip
