// distribution boxbackup-0.10 (svn version: 494)
//  
// Copyright (c) 2003 - 2006
//      Ben Summers and contributors.  All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. All use of this software and associated advertising materials must 
//    display the following acknowledgment:
//        This product includes software developed by Ben Summers.
// 4. The names of the Authors may not be used to endorse or promote
//    products derived from this software without specific prior written
//    permission.
// 
// [Where legally impermissible the Authors do not disclaim liability for 
// direct physical injury or death caused solely by defects in the software 
// unless it is modified by a third party.]
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//  
//  
//  
// --------------------------------------------------------------------------
//
// File
//		Name:    BackupStoreInfo.cpp
//		Purpose: Main backup store information storage
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------

#include "Box.h"

#include <algorithm>

#include "BackupStoreInfo.h"
#include "BackupStoreException.h"
#include "RaidFileWrite.h"
#include "RaidFileRead.h"

#include "MemLeakFindOn.h"

// set packing to one byte
#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
#include "BeginStructPackForWire.h"
#else
BEGIN_STRUCTURE_PACKING_FOR_WIRE
#endif

// ******************
// make sure the defaults in CreateNew are modified!
// ******************
typedef struct
{
	int32_t mMagicValue;	// also the version number
	int32_t mAccountID;
	int64_t mClientStoreMarker;
	int64_t mLastObjectIDUsed;
	int64_t mBlocksUsed;
	int64_t mBlocksInOldFiles;
	int64_t mBlocksInDeletedFiles;
	int64_t mBlocksInDirectories;
	int64_t mBlocksSoftLimit;
	int64_t mBlocksHardLimit;
	uint32_t mCurrentMarkNumber;
	uint32_t mOptionsPresent;		// bit mask of optional elements present
	int64_t mNumberDeletedDirectories;
	// Then loads of int64_t IDs for the deleted directories
} info_StreamFormat;

#define INFO_MAGIC_VALUE	0x34832476

// Use default packing
#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
#include "EndStructPackForWire.h"
#else
END_STRUCTURE_PACKING_FOR_WIRE
#endif

#ifdef NDEBUG
	#define 	NUM_DELETED_DIRS_BLOCK	256
#else
	#define 	NUM_DELETED_DIRS_BLOCK	2
#endif

#define INFO_FILENAME	"info"

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::BackupStoreInfo()
//		Purpose: Default constructor
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
BackupStoreInfo::BackupStoreInfo()
	: mAccountID(-1),
	  mDiscSet(-1),
	  mReadOnly(true),
	  mIsModified(false),
	  mClientStoreMarker(0),
	  mLastObjectIDUsed(-1),
	  mBlocksUsed(0),
	  mBlocksInOldFiles(0),
	  mBlocksInDeletedFiles(0)
{
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::~BackupStoreInfo
//		Purpose: Destructor
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
BackupStoreInfo::~BackupStoreInfo()
{
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::CreateNew(int32_t, const std::string &, int)
//		Purpose: Create a new info file on disc
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit)
{
	// Initial header (is entire file)
	info_StreamFormat hdr = {
		htonl(INFO_MAGIC_VALUE), // mMagicValue
		htonl(AccountID), // mAccountID
		0, // mClientStoreMarker
		box_hton64(1), // mLastObjectIDUsed (which is the root directory)
		0, // mBlocksUsed
		0, // mBlocksInOldFiles
		0, // mBlocksInDeletedFiles
		0, // mBlocksInDirectories
		box_hton64(BlockSoftLimit), // mBlocksSoftLimit
		box_hton64(BlockHardLimit), // mBlocksHardLimit
		0, // mCurrentMarkNumber
		0, // mOptionsPresent
		0 // mNumberDeletedDirectories
	};
	
	// Generate the filename
	ASSERT(rRootDir[rRootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR);
	std::string fn(rRootDir + INFO_FILENAME);
	
	// Open the file for writing
	RaidFileWrite rf(DiscSet, fn);
	rf.Open(false);		// no overwriting, as this is a new file
	
	// Write header
	rf.Write(&hdr, sizeof(hdr));
	
	// Commit it to disc, converting it to RAID now
	rf.Commit(true);
	
	// Done.
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::Load(int32_t, const std::string &, int, bool)
//		Purpose: Loads the info from disc, given the root information. Can be marked as read only.
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID, const std::string &rRootDir, int DiscSet, bool ReadOnly, int64_t *pRevisionID)
{
	// Generate the filename
	std::string fn(rRootDir + DIRECTORY_SEPARATOR INFO_FILENAME);
	
	// Open the file for reading (passing on optional request for revision ID)
	std::auto_ptr<RaidFileRead> rf(RaidFileRead::Open(DiscSet, fn, pRevisionID));
	
	// Read in a header
	info_StreamFormat hdr;
	if(!rf->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */))
	{
		THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo)
	}
	
	// Check it
	if(ntohl(hdr.mMagicValue) != INFO_MAGIC_VALUE || (int32_t)ntohl(hdr.mAccountID) != AccountID)
	{
		THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad)
	}
	
	// Make new object
	std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo);
	
	// Put in basic location info
	info->mAccountID = AccountID;
	info->mDiscSet = DiscSet;
	info->mFilename = fn;
	info->mReadOnly = ReadOnly;
	
	// Insert info from file
	info->mClientStoreMarker	= box_ntoh64(hdr.mClientStoreMarker);
	info->mLastObjectIDUsed		= box_ntoh64(hdr.mLastObjectIDUsed);
	info->mBlocksUsed 			= box_ntoh64(hdr.mBlocksUsed);
	info->mBlocksInOldFiles 	= box_ntoh64(hdr.mBlocksInOldFiles);
	info->mBlocksInDeletedFiles	= box_ntoh64(hdr.mBlocksInDeletedFiles);
	info->mBlocksInDirectories	= box_ntoh64(hdr.mBlocksInDirectories);
	info->mBlocksSoftLimit		= box_ntoh64(hdr.mBlocksSoftLimit);
	info->mBlocksHardLimit		= box_ntoh64(hdr.mBlocksHardLimit);
	
	// Load up array of deleted objects
	int64_t numDelObj = box_ntoh64(hdr.mNumberDeletedDirectories);
	
	// Then load them in
	if(numDelObj > 0)
	{
		int64_t objs[NUM_DELETED_DIRS_BLOCK];
		
		int64_t toload = numDelObj;
		while(toload > 0)
		{
			// How many in this one?
			int b = (toload > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(toload));
			
			if(!rf->ReadFullBuffer(objs, b * sizeof(int64_t), 0 /* not interested in bytes read if this fails */))
			{
				THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo)
			}
			
			// Add them
			for(int t = 0; t < b; ++t)
			{
				info->mDeletedDirectories.push_back(box_ntoh64(objs[t]));
			}
			
			// Number loaded
			toload -= b;
		}
	}

	// Final check
	if(static_cast<int64_t>(info->mDeletedDirectories.size()) != numDelObj)
	{
		THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad)
	}
	
	// return it to caller
	return info;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::CreateForRegeneration(...)
//		Purpose: Return an object which can be used to save for regeneration.
//		Created: 23/4/04
//
// --------------------------------------------------------------------------
std::auto_ptr<BackupStoreInfo> BackupStoreInfo::CreateForRegeneration(int32_t AccountID, const std::string &rRootDir,
	int DiscSet, int64_t LastObjectID, int64_t BlocksUsed, int64_t BlocksInOldFiles,
	int64_t BlocksInDeletedFiles, int64_t BlocksInDirectories, int64_t BlockSoftLimit, int64_t BlockHardLimit)
{
	// Generate the filename
	std::string fn(rRootDir + DIRECTORY_SEPARATOR INFO_FILENAME);
	
	// Make new object
	std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo);
	
	// Put in basic info
	info->mAccountID = AccountID;
	info->mDiscSet = DiscSet;
	info->mFilename = fn;
	info->mReadOnly = false;
	
	// Insert info starting info
	info->mClientStoreMarker	= 0;
	info->mLastObjectIDUsed		= LastObjectID;
	info->mBlocksUsed 			= BlocksUsed;
	info->mBlocksInOldFiles 	= BlocksInOldFiles;
	info->mBlocksInDeletedFiles	= BlocksInDeletedFiles;
	info->mBlocksInDirectories	= BlocksInDirectories;
	info->mBlocksSoftLimit		= BlockSoftLimit;
	info->mBlocksHardLimit		= BlockHardLimit;
	
	// return it to caller
	return info;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::Save()
//		Purpose: Save modified info back to disc
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::Save()
{
	// Make sure we're initialised (although should never come to this)
	if(mFilename.empty() || mAccountID == -1 || mDiscSet == -1)
	{
		THROW_EXCEPTION(BackupStoreException, Internal)
	}

	// Can we do this?
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	
	// Then... open a write file
	RaidFileWrite rf(mDiscSet, mFilename);
	rf.Open(true);		// allow overwriting
	
	// Make header
	info_StreamFormat hdr;
	hdr.mMagicValue 				= htonl(INFO_MAGIC_VALUE);
	hdr.mAccountID 					= htonl(mAccountID);
	hdr.mClientStoreMarker			= box_hton64(mClientStoreMarker);
	hdr.mLastObjectIDUsed			= box_hton64(mLastObjectIDUsed);
	hdr.mBlocksUsed 				= box_hton64(mBlocksUsed);
	hdr.mBlocksInOldFiles 			= box_hton64(mBlocksInOldFiles);
	hdr.mBlocksInDeletedFiles 		= box_hton64(mBlocksInDeletedFiles);
	hdr.mBlocksInDirectories		= box_hton64(mBlocksInDirectories);
	hdr.mBlocksSoftLimit			= box_hton64(mBlocksSoftLimit);
	hdr.mBlocksHardLimit			= box_hton64(mBlocksHardLimit);
	hdr.mCurrentMarkNumber			= 0;
	hdr.mOptionsPresent				= 0;
	hdr.mNumberDeletedDirectories	= box_hton64(mDeletedDirectories.size());
	
	// Write header
	rf.Write(&hdr, sizeof(hdr));
	
	// Write the deleted object list
	if(mDeletedDirectories.size() > 0)
	{
		int64_t objs[NUM_DELETED_DIRS_BLOCK];
		
		int tosave = mDeletedDirectories.size();
		std::vector<int64_t>::iterator i(mDeletedDirectories.begin());
		while(tosave > 0)
		{
			// How many in this one?
			int b = (tosave > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(tosave));
			
			// Add them
			for(int t = 0; t < b; ++t)
			{
				ASSERT(i != mDeletedDirectories.end());
				objs[t] = box_hton64((*i));
				i++;
			}

			// Write			
			rf.Write(objs, b * sizeof(int64_t));
			
			// Number saved
			tosave -= b;
		}
	}

	// Commit it to disc, converting it to RAID now
	rf.Commit(true);
	
	// Mark is as not modified
	mIsModified = false;
}



// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::ChangeBlocksUsed(int32_t)
//		Purpose: Change number of blocks used, by a delta amount
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	if((mBlocksUsed + Delta) < 0)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative)
	}
	
	mBlocksUsed += Delta;
	
	mIsModified = true;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::ChangeBlocksInOldFiles(int32_t)
//		Purpose: Change number of blocks in old files, by a delta amount
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	if((mBlocksInOldFiles + Delta) < 0)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative)
	}
	
	mBlocksInOldFiles += Delta;
	
	mIsModified = true;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::ChangeBlocksInDeletedFiles(int32_t)
//		Purpose: Change number of blocks in deleted files, by a delta amount
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	if((mBlocksInDeletedFiles + Delta) < 0)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative)
	}
	
	mBlocksInDeletedFiles += Delta;
	
	mIsModified = true;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::ChangeBlocksInDirectories(int32_t)
//		Purpose: Change number of blocks in directories, by a delta amount
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksInDirectories(int64_t Delta)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	if((mBlocksInDirectories + Delta) < 0)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative)
	}
	
	mBlocksInDirectories += Delta;
	
	mIsModified = true;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::CorrectAllUsedValues(int64_t, int64_t, int64_t, int64_t)
//		Purpose: Set all the usage counts to specific values -- use for correcting in housekeeping
//				 if something when wrong during the backup connection, and the store info wasn't
//				 saved back to disc.
//		Created: 15/12/03
//
// --------------------------------------------------------------------------
void BackupStoreInfo::CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	
	// Set the values
	mBlocksUsed = Used;
	mBlocksInOldFiles = InOldFiles;
	mBlocksInDeletedFiles = InDeletedFiles;
	mBlocksInDirectories = InDirectories;
	
	mIsModified = true;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::AddDeletedDirectory(int64_t)
//		Purpose: Add a directory ID to the deleted list
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::AddDeletedDirectory(int64_t DirID)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	
	mDeletedDirectories.push_back(DirID);
	
	mIsModified = true;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::RemovedDeletedDirectory(int64_t)
//		Purpose: Remove a directory from the deleted list
//		Created: 2003/08/28
//
// --------------------------------------------------------------------------
void BackupStoreInfo::RemovedDeletedDirectory(int64_t DirID)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	
	std::vector<int64_t>::iterator i(std::find(mDeletedDirectories.begin(), mDeletedDirectories.end(), DirID));
	if(i == mDeletedDirectories.end())
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoDirNotInList)
	}
	mDeletedDirectories.erase(i);
	
	mIsModified = true;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::ChangeLimits(int64_t, int64_t)
//		Purpose: Change the soft and hard limits
//		Created: 15/12/03
//
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}

	mBlocksSoftLimit = BlockSoftLimit;
	mBlocksHardLimit = BlockHardLimit;
	
	mIsModified = true;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::AllocateObjectID()
//		Purpose: Allocate an ID for a new object in the store.
//		Created: 2003/09/03
//
// --------------------------------------------------------------------------
int64_t BackupStoreInfo::AllocateObjectID()
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	if(mLastObjectIDUsed < 0)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoNotInitialised)
	}
	
	// Return the next object ID
	return ++mLastObjectIDUsed;
	
	mIsModified = true;
}



// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupStoreInfo::SetClientStoreMarker(int64_t)
//		Purpose: Sets the client store marker
//		Created: 2003/10/29
//
// --------------------------------------------------------------------------
void BackupStoreInfo::SetClientStoreMarker(int64_t ClientStoreMarker)
{
	if(mReadOnly)
	{
		THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
	}
	
	mClientStoreMarker = ClientStoreMarker;
	
	mIsModified = true;
}



