// 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:    BackupQueries.cpp
//		Purpose: Perform various queries on the backup store server.
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------

#include "Box.h"

#ifdef HAVE_UNISTD_H
	#include <unistd.h>
#endif

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef HAVE_DIRENT_H
	#include <dirent.h>
#endif

#include <set>

#include "BackupQueries.h"
#include "Utils.h"
#include "Configuration.h"
#include "autogen_BackupProtocolClient.h"
#include "BackupStoreFilenameClear.h"
#include "BackupStoreDirectory.h"
#include "IOStream.h"
#include "BoxTimeToText.h"
#include "FileStream.h"
#include "BackupStoreFile.h"
#include "TemporaryDirectory.h"
#include "FileModificationTime.h"
#include "BackupClientFileAttributes.h"
#include "CommonException.h"
#include "BackupClientRestore.h"
#include "BackupStoreException.h"
#include "ExcludeList.h"
#include "BackupClientMakeExcludeList.h"

#include "MemLeakFindOn.h"

#define COMPARE_RETURN_SAME			1
#define COMPARE_RETURN_DIFFERENT	2
#define COMPARE_RETURN_ERROR		3


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::BackupQueries()
//		Purpose: Constructor
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
BackupQueries::BackupQueries(BackupProtocolClient &rConnection, const Configuration &rConfiguration)
	: mrConnection(rConnection),
	  mrConfiguration(rConfiguration),
	  mQuitNow(false),
	  mRunningAsRoot(false),
	  mWarnedAboutOwnerAttributes(false),
	  mReturnCode(0)		// default return code
{
	mRunningAsRoot = (::geteuid() == 0);
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::~BackupQueries()
//		Purpose: Destructor
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
BackupQueries::~BackupQueries()
{
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::DoCommand(const char *)
//		Purpose: Perform a command
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
void BackupQueries::DoCommand(const char *Command)
{
	// is the command a shell command?
	if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
	{
		// Yes, run shell command
		::system(Command + 3);
		return;
	}

	// split command into components
	std::vector<std::string> cmdElements;
	std::string options;
	{
		const char *c = Command;
		bool inQuoted = false;
		bool inOptions = false;
		
		std::string s;
		while(*c != 0)
		{
			// Terminating char?
			if(*c == ((inQuoted)?'"':' '))
			{
				if(!s.empty()) cmdElements.push_back(s);
				s.resize(0);
				inQuoted = false;
				inOptions = false;
			}
			else
			{
				// No. Start of quoted parameter?
				if(s.empty() && *c == '"')
				{
					inQuoted = true;
				}
				// Start of options?
				else if(s.empty() && *c == '-')
				{
					inOptions = true;
				}
				else
				{
					if(inOptions)
					{
						// Option char
						options += *c;
					}
					else
					{
						// Normal string char
						s += *c;
					}
				}
			}
		
			++c;
		}
		if(!s.empty()) cmdElements.push_back(s);
	}
	
	// Check...
	if(cmdElements.size() < 1)
	{
		// blank command
		return;
	}
	
	// Data about commands
	static const char *commandNames[] = {"quit", "exit", "list",	 "pwd", "cd", "lcd",	"sh", "getobject", "get", "compare", "restore", "help", "usage", "undelete", 0};
	static const char *validOptions[] = {"",	 "",	 "rodIFtsh", "",	   "od", "",	"",	  "",		   "i",   "alcqE",   "dri",     "",     "",      "",		 0};
	#define COMMAND_Quit		0
	#define COMMAND_Exit		1
	#define COMMAND_List		2
	#define COMMAND_pwd			3
	#define COMMAND_cd			4
	#define COMMAND_lcd			5
	#define COMMAND_sh			6
	#define COMMAND_GetObject	7
	#define COMMAND_Get			8
	#define COMMAND_Compare		9
	#define COMMAND_Restore		10
	#define COMMAND_Help		11
	#define COMMAND_Usage		12
	#define COMMAND_Undelete	13
	static const char *alias[] = {"ls",			0};
	static const int aliasIs[] = {COMMAND_List, 0};
	
	// Work out which command it is...
	int cmd = 0;
	while(commandNames[cmd] != 0 && ::strcmp(cmdElements[0].c_str(), commandNames[cmd]) != 0)
	{
		cmd++;
	}
	if(commandNames[cmd] == 0)
	{
		// Check for aliases
		int a;
		for(a = 0; alias[a] != 0; ++a)
		{
			if(::strcmp(cmdElements[0].c_str(), alias[a]) == 0)
			{
				// Found an alias
				cmd = aliasIs[a];
				break;
			}
		}
	
		// No such command
		if(alias[a] == 0)
		{
			printf("Unrecognised command: %s\n", Command);
			return;
		}
	}

	// Arguments
	std::vector<std::string> args(cmdElements.begin() + 1, cmdElements.end());

	// Set up options
	bool opts[256];
	for(int o = 0; o < 256; ++o) opts[o] = false;
	// BLOCK
	{
		// options
		const char *c = options.c_str();
		while(*c != 0)
		{
			// Valid option?
			if(::strchr(validOptions[cmd], *c) == NULL)
			{
				printf("Invalid option '%c' for command %s\n", *c, commandNames[cmd]);
				return;
			}
			opts[(int)*c] = true;
			++c;
		}
	}

	if(cmd != COMMAND_Quit && cmd != COMMAND_Exit)
	{
		// If not a quit command, set the return code to zero
		SetReturnCode(0);
	}

	// Handle command
	switch(cmd)
	{
	case COMMAND_Quit:
	case COMMAND_Exit:
		mQuitNow = true;
		break;
		
	case COMMAND_List:
		CommandList(args, opts);
		break;
		
	case COMMAND_pwd:
		{
			// Simple implementation, so do it here
			printf("%s (%08llx)\n", 
				GetCurrentDirectoryName().c_str(), 
				(long long)GetCurrentDirectoryID());
		}
		break;

	case COMMAND_cd:
		CommandChangeDir(args, opts);
		break;
		
	case COMMAND_lcd:
		CommandChangeLocalDir(args);
		break;
		
	case COMMAND_sh:
		printf("The command to run must be specified as an argument.\n");
		break;
		
	case COMMAND_GetObject:
		CommandGetObject(args, opts);
		break;
		
	case COMMAND_Get:
		CommandGet(args, opts);
		break;
		
	case COMMAND_Compare:
		CommandCompare(args, opts);
		break;
		
	case COMMAND_Restore:
		CommandRestore(args, opts);
		break;
		
	case COMMAND_Usage:
		CommandUsage();
		break;
		
	case COMMAND_Help:
		CommandHelp(args);
		break;

	case COMMAND_Undelete:
		CommandUndelete(args, opts);
		break;
		
	default:
		break;
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandList(const std::vector<std::string> &, const bool *)
//		Purpose: List directories (optionally recursive)
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts)
{
	#define LIST_OPTION_RECURSIVE		'r'
	#define LIST_OPTION_ALLOWOLD		'o'
	#define LIST_OPTION_ALLOWDELETED	'd'
	#define LIST_OPTION_NOOBJECTID		'I'
	#define LIST_OPTION_NOFLAGS			'F'
	#define LIST_OPTION_TIMES			't'
	#define LIST_OPTION_SIZEINBLOCKS	's'
	#define LIST_OPTION_DISPLAY_HASH	'h'

	// default to using the current directory
	int64_t rootDir = GetCurrentDirectoryID();

	// name of base directory
	std::string listRoot;	// blank

	// Got a directory in the arguments?
	if(args.size() > 0)
	{
#ifdef WIN32
		std::string storeDirEncoded;
		if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded))
			return;
#else
		const std::string& storeDirEncoded(args[0]);
#endif
	
		// Attempt to find the directory
		rootDir = FindDirectoryObjectID(storeDirEncoded, 
			opts[LIST_OPTION_ALLOWOLD], 
			opts[LIST_OPTION_ALLOWDELETED]);

		if(rootDir == 0)
		{
			printf("Directory '%s' not found on store\n",
				args[0].c_str());
			return;
		}
	}
	
	// List it
	List(rootDir, listRoot, opts, true /* first level to list */);
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandList2(int64_t, const std::string &, const bool *)
//		Purpose: Do the actual listing of directories and files
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel)
{
	// Generate exclude flags
	int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
	if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
	if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;

	// Do communication
	mrConnection.QueryListDirectory(
			DirID,
			BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,	// both files and directories
			excludeFlags,
			true /* want attributes */);

	// Retrieve the directory from the stream following
	BackupStoreDirectory dir;
	std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
	dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());

	// Then... display everything
	BackupStoreDirectory::Iterator i(dir);
	BackupStoreDirectory::Entry *en = 0;
	while((en = i.Next()) != 0)
	{
		// Display this entry
		BackupStoreFilenameClear clear(en->GetName());
		
		// Object ID?
		if(!opts[LIST_OPTION_NOOBJECTID])
		{
			// add object ID to line
#ifdef _MSC_VER
			printf("%08I64x ", (int64_t)en->GetObjectID());
#else
			printf("%08llx ", (long long)en->GetObjectID());
#endif
		}
		
		// Flags?
		if(!opts[LIST_OPTION_NOFLAGS])
		{
			static const char *flags = BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES;
			char displayflags[16];
			// make sure f is big enough
			ASSERT(sizeof(displayflags) >= sizeof(BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES) + 3);
			// Insert flags
			char *f = displayflags;
			const char *t = flags;
			int16_t en_flags = en->GetFlags();
			while(*t != 0)
			{
				*f = ((en_flags&1) == 0)?'-':*t;
				en_flags >>= 1;
				f++;
				t++;
			}
			// attributes flags
			*(f++) = (en->HasAttributes())?'a':'-';

			// terminate
			*(f++) = ' ';
			*(f++) = '\0';
			printf(displayflags);
			
			if(en_flags != 0)
			{
				printf("[ERROR: Entry has additional flags set] ");
			}
		}
		
		if(opts[LIST_OPTION_TIMES])
		{
			// Show times...
			std::string time = BoxTimeToISO8601String(
				en->GetModificationTime());
			printf("%s ", time.c_str());
		}
		
		if(opts[LIST_OPTION_DISPLAY_HASH])
		{
#ifdef _MSC_VER
			printf("%016I64x ", (int64_t)en->GetAttributesHash());
#else
			printf("%016llx ", (long long)en->GetAttributesHash());
#endif
		}
		
		if(opts[LIST_OPTION_SIZEINBLOCKS])
		{
#ifdef _MSC_VER
			printf("%05I64d ", (int64_t)en->GetSizeInBlocks());
#else
			printf("%05lld ", (long long)en->GetSizeInBlocks());
#endif
		}
		
		// add name
		if(!FirstLevel)
		{
#ifdef WIN32
			std::string listRootDecoded;
			if(!ConvertUtf8ToConsole(rListRoot.c_str(), 
				listRootDecoded)) return;
			printf("%s/", listRootDecoded.c_str());
#else
			printf("%s/", rListRoot.c_str());
#endif
		}
		
#ifdef WIN32
		{
			std::string fileName;
			if(!ConvertUtf8ToConsole(
				clear.GetClearFilename().c_str(), fileName))
				return;
			printf("%s", fileName.c_str());
		}
#else
		printf("%s", clear.GetClearFilename().c_str());
#endif
		
		if(!en->GetName().IsEncrypted())
		{
			printf("[FILENAME NOT ENCRYPTED]");
		}

		printf("\n");
		
		// Directory?
		if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0)
		{
			// Recurse?
			if(opts[LIST_OPTION_RECURSIVE])
			{
				std::string subroot(rListRoot);
				if(!FirstLevel) subroot += '/';
				subroot += clear.GetClearFilename();
				List(en->GetObjectID(), subroot, opts, false /* not the first level to list */);
			}
		}
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::FindDirectoryObjectID(const std::string &)
//		Purpose: Find the object ID of a directory on the store, or return 0 for not found.
//				 If pStack != 0, the object is set to the stack of directories.
//				 Will start from the current directory stack.
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, bool AllowOldVersion,
			bool AllowDeletedDirs, std::vector<std::pair<std::string, int64_t> > *pStack)
{
	// Split up string into elements
	std::vector<std::string> dirElements;
	SplitString(rDirName, '/', dirElements);

	// Start from current stack, or root, whichever is required
	std::vector<std::pair<std::string, int64_t> > stack;
	int64_t dirID = BackupProtocolClientListDirectory::RootDirectory;
	if(rDirName.size() > 0 && rDirName[0] == '/')
	{
		// Root, do nothing
	}
	else
	{
		// Copy existing stack
		stack = mDirStack;
		if(stack.size() > 0)
		{
			dirID = stack[stack.size() - 1].second;
		}
	}

	// Generate exclude flags
	int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
	if(!AllowOldVersion) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
	if(!AllowDeletedDirs) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;

	// Read directories
	for(unsigned int e = 0; e < dirElements.size(); ++e)
	{
		if(dirElements[e].size() > 0)
		{
			if(dirElements[e] == ".")
			{
				// Ignore.
			}
			else if(dirElements[e] == "..")
			{
				// Up one!
				if(stack.size() > 0)
				{
					// Remove top element
					stack.pop_back();
					
					// New dir ID
					dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolClientListDirectory::RootDirectory;
				}
				else
				{	
					// At root anyway
					dirID = BackupProtocolClientListDirectory::RootDirectory;
				}
			}
			else
			{
				// Not blank element. Read current directory.
				std::auto_ptr<BackupProtocolClientSuccess> dirreply(mrConnection.QueryListDirectory(
						dirID,
						BackupProtocolClientListDirectory::Flags_Dir,	// just directories
						excludeFlags,
						true /* want attributes */));

				// Retrieve the directory from the stream following
				BackupStoreDirectory dir;
				std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
				dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());

				// Then... find the directory within it
				BackupStoreDirectory::Iterator i(dir);
				BackupStoreFilenameClear dirname(dirElements[e]);
				BackupStoreDirectory::Entry *en = i.FindMatchingClearName(dirname);
				if(en == 0)
				{
					// Not found
					return 0;
				}
				
				// Object ID for next round of searching
				dirID = en->GetObjectID();

				// Push onto stack
				stack.push_back(std::pair<std::string, int64_t>(dirElements[e], dirID));
			}
		}
	}
	
	// If required, copy the new stack to the caller
	if(pStack)
	{
		*pStack = stack;
	}

	return dirID;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::GetCurrentDirectoryID()
//		Purpose: Returns the ID of the current directory
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
int64_t BackupQueries::GetCurrentDirectoryID()
{
	// Special case for root
	if(mDirStack.size() == 0)
	{
		return BackupProtocolClientListDirectory::RootDirectory;
	}
	
	// Otherwise, get from the last entry on the stack
	return mDirStack[mDirStack.size() - 1].second;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::GetCurrentDirectoryName()
//		Purpose: Gets the name of the current directory
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
std::string BackupQueries::GetCurrentDirectoryName()
{
	// Special case for root
	if(mDirStack.size() == 0)
	{
		return std::string("/");
	}

	// Build path
	std::string r;
	for(unsigned int l = 0; l < mDirStack.size(); ++l)
	{
		r += "/";
#ifdef WIN32
		std::string dirName;
		if(!ConvertUtf8ToConsole(mDirStack[l].first.c_str(), dirName))
			return "error";
		r += dirName;
#else
		r += mDirStack[l].first;
#endif
	}
	
	return r;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandChangeDir(const std::vector<std::string> &)
//		Purpose: Change directory command
//		Created: 2003/10/10
//
// --------------------------------------------------------------------------
void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const bool *opts)
{
	if(args.size() != 1 || args[0].size() == 0)
	{
		printf("Incorrect usage.\ncd [-o] [-d] <directory>\n");
		return;
	}

#ifdef WIN32
	std::string dirName;
	if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return;
#else
	const std::string& dirName(args[0]);
#endif
	
	std::vector<std::pair<std::string, int64_t> > newStack;
	int64_t id = FindDirectoryObjectID(dirName, opts['o'], opts['d'], 
		&newStack);
	
	if(id == 0)
	{
		printf("Directory '%s' not found\n", args[0].c_str());
		return;
	}
	
	// Store new stack
	mDirStack = newStack;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &)
//		Purpose: Change local directory command
//		Created: 2003/10/11
//
// --------------------------------------------------------------------------
void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args)
{
	if(args.size() != 1 || args[0].size() == 0)
	{
		printf("Incorrect usage.\nlcd <local-directory>\n");
		return;
	}
	
	// Try changing directory
#ifdef WIN32
	std::string dirName;
	if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return;
	int result = ::chdir(dirName.c_str());
#else
	int result = ::chdir(args[0].c_str());
#endif
	if(result != 0)
	{
		printf((errno == ENOENT || errno == ENOTDIR)?"Directory '%s' does not exist\n":"Error changing dir to '%s'\n",
			args[0].c_str());
		return;
	}
	
	// Report current dir
	char wd[PATH_MAX];
	if(::getcwd(wd, PATH_MAX) == 0)
	{
		printf("Error getting current directory\n");
		return;
	}

#ifdef WIN32
	if(!ConvertUtf8ToConsole(wd, dirName)) return;
	printf("Local current directory is now '%s'\n", dirName.c_str());
#else
	printf("Local current directory is now '%s'\n", wd);
#endif
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandGetObject(const std::vector<std::string> &, const bool *)
//		Purpose: Gets an object without any translation.
//		Created: 2003/10/11
//
// --------------------------------------------------------------------------
void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const bool *opts)
{
	// Check args
	if(args.size() != 2)
	{
		printf("Incorrect usage.\ngetobject <object-id> <local-filename>\n");
		return;
	}
	
	int64_t id = ::strtoll(args[0].c_str(), 0, 16);
	if(id == LLONG_MIN || id == LLONG_MAX || id == 0)
	{
		printf("Not a valid object ID (specified in hex)\n");
		return;
	}
	
	// Does file exist?
	struct stat st;
	if(::stat(args[1].c_str(), &st) == 0 || errno != ENOENT)
	{
		printf("The local file %s already exists\n", args[1].c_str());
		return;
	}
	
	// Open file
	FileStream out(args[1].c_str(), O_WRONLY | O_CREAT | O_EXCL);
	
	// Request that object
	try
	{
		// Request object
		std::auto_ptr<BackupProtocolClientSuccess> getobj(mrConnection.QueryGetObject(id));
		if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject)
		{
			// Stream that object out to the file
			std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
			objectStream->CopyStreamTo(out);
			
			printf("Object ID %08llx fetched successfully.\n", id);
		}
		else
		{
			printf("Object does not exist on store.\n");
			::unlink(args[1].c_str());
		}
	}
	catch(...)
	{
		::unlink(args[1].c_str());
		printf("Error occured fetching object.\n");
	}
}



// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandGet(const std::vector<std::string> &, const bool *)
//		Purpose: Command to get a file from the store
//		Created: 2003/10/12
//
// --------------------------------------------------------------------------
void BackupQueries::CommandGet(const std::vector<std::string> &args, const bool *opts)
{
	// At least one argument?
	// Check args
	if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2)
	{
		printf("Incorrect usage.\n"
			"get <remote-filename> [<local-filename>] or\n"
			"get -i <object-id> <local-filename>\n");
		return;
	}

	// Find object ID somehow
	int64_t id;
	std::string localName;
	// BLOCK
	{
		// Need to look it up in the current directory
		mrConnection.QueryListDirectory(
				GetCurrentDirectoryID(),
				BackupProtocolClientListDirectory::Flags_File,	// just files
				(opts['i'])?(BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING):(BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted), // only current versions
				false /* don't want attributes */);

		// Retrieve the directory from the stream following
		BackupStoreDirectory dir;
		std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
		dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());

		if(opts['i'])
		{
			// Specified as ID. 
			id = ::strtoll(args[0].c_str(), 0, 16);
			if(id == LLONG_MIN || id == LLONG_MAX || id == 0)
			{
				printf("Not a valid object ID (specified in hex)\n");
				return;
			}
			
			// Check that the item is actually in the directory
			if(dir.FindEntryByID(id) == 0)
			{
				printf("ID '%08llx' not found in current directory on store.\n(You can only download objects by ID from the current directory.)\n", id);
				return;
			}
			
			// Must have a local name in the arguments (check at beginning of function ensures this)
			localName = args[1];
		}
		else
		{				
			// Specified by name, find the object in the directory to get the ID
			BackupStoreDirectory::Iterator i(dir);
#ifdef WIN32
			std::string fileName;
			if(!ConvertConsoleToUtf8(args[0].c_str(), fileName))
				return;
			BackupStoreFilenameClear fn(fileName);
#else
			BackupStoreFilenameClear fn(args[0]);
#endif
			BackupStoreDirectory::Entry *en = i.FindMatchingClearName(fn);
			
			if(en == 0)
			{
				printf("Filename '%s' not found in current directory on store.\n(Subdirectories in path not searched.)\n", args[0].c_str());
				return;
			}
			
			id = en->GetObjectID();
			
			// Local name is the last argument, which is either the looked up filename, or
			// a filename specified by the user.
			localName = args[args.size() - 1];
		}
	}
	
	// Does local file already exist? (don't want to overwrite)
	struct stat st;
	if(::stat(localName.c_str(), &st) == 0 || errno != ENOENT)
	{
		printf("The local file %s already exists, will not overwrite it.\n", localName.c_str());
		return;
	}
	
	// Request it from the store
	try
	{
		// Request object
		mrConnection.QueryGetFile(GetCurrentDirectoryID(), id);

		// Stream containing encoded file
		std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
		
		// Decode it
		BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout());

		// Done.
		printf("Object ID %08llx fetched sucessfully.\n", id);
	}
	catch(...)
	{
		::unlink(localName.c_str());
		printf("Error occured fetching file.\n");
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CompareParams::CompareParams()
//		Purpose: Constructor
//		Created: 29/1/04
//
// --------------------------------------------------------------------------
BackupQueries::CompareParams::CompareParams()
	: mQuickCompare(false),
	  mIgnoreExcludes(false),
	  mDifferences(0),
	  mDifferencesExplainedByModTime(0),
	  mExcludedDirs(0),
	  mExcludedFiles(0),
	  mpExcludeFiles(0),
	  mpExcludeDirs(0),
	  mLatestFileUploadTime(0)
{
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CompareParams::~CompareParams()
//		Purpose: Destructor
//		Created: 29/1/04
//
// --------------------------------------------------------------------------
BackupQueries::CompareParams::~CompareParams()
{
	DeleteExcludeLists();
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CompareParams::DeleteExcludeLists()
//		Purpose: Delete the include lists contained
//		Created: 29/1/04
//
// --------------------------------------------------------------------------
void BackupQueries::CompareParams::DeleteExcludeLists()
{
	if(mpExcludeFiles != 0)
	{
		delete mpExcludeFiles;
		mpExcludeFiles = 0;
	}
	if(mpExcludeDirs != 0)
	{
		delete mpExcludeDirs;
		mpExcludeDirs = 0;
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandCompare(const std::vector<std::string> &, const bool *)
//		Purpose: Command to compare data on the store with local data
//		Created: 2003/10/12
//
// --------------------------------------------------------------------------
void BackupQueries::CommandCompare(const std::vector<std::string> &args, const bool *opts)
{
	// Parameters, including count of differences
	BackupQueries::CompareParams params;
	params.mQuickCompare = opts['q'];
	params.mIgnoreExcludes = opts['E'];
	
	// Try and work out the time before which all files should be on the server
	{
		std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR);
		syncTimeFilename += "last_sync_start";
		// Stat it to get file time
		struct stat st;
		if(::stat(syncTimeFilename.c_str(), &st) == 0)
		{
			// Files modified after this time shouldn't be on the server, so report errors slightly differently
			params.mLatestFileUploadTime = FileModificationTime(st)
					- SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge"));
		}
		else
		{
			printf("Warning: couldn't determine the time of the last synchronisation -- checks not performed.\n");
		}
	}

	// Quick compare?
	if(params.mQuickCompare)
	{
		printf("WARNING: Quick compare used -- file attributes are not checked.\n");
	}
	
	if(!opts['l'] && opts['a'] && args.size() == 0)
	{
		// Compare all locations
		const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations"));
		for(std::list<std::pair<std::string, Configuration> >::const_iterator i = locations.mSubConfigurations.begin();
				i != locations.mSubConfigurations.end(); ++i)
		{
			CompareLocation(i->first, params);
		}
	}
	else if(opts['l'] && !opts['a'] && args.size() == 1)
	{
		// Compare one location
		CompareLocation(args[0], params);
	}
	else if(!opts['l'] && !opts['a'] && args.size() == 2)
	{
		// Compare directory to directory
		
		// Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list
		if(!params.mIgnoreExcludes)
		{
			printf("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes\n");
			return;
		}
		else
		{
			// Do compare
			Compare(args[0], args[1], params);
		}
	}
	else
	{
		printf("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>\n");
		return;
	}
	
	printf("\n[ %d (of %d) differences probably due to file modifications after the last upload ]\nDifferences: %d (%d dirs excluded, %d files excluded)\n",
		params.mDifferencesExplainedByModTime, params.mDifferences, params.mDifferences, params.mExcludedDirs, params.mExcludedFiles);
	
	// Set return code?
	if(opts['c'])
	{
		SetReturnCode((params.mDifferences == 0)?COMPARE_RETURN_SAME:COMPARE_RETURN_DIFFERENT);
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &)
//		Purpose: Compare a location
//		Created: 2003/10/13
//
// --------------------------------------------------------------------------
void BackupQueries::CompareLocation(const std::string &rLocation, BackupQueries::CompareParams &rParams)
{
	// Find the location's sub configuration
	const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations"));
	if(!locations.SubConfigurationExists(rLocation.c_str()))
	{
		printf("Location %s does not exist.\n", rLocation.c_str());
		return;
	}
	const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str()));
	
	try
	{
		// Generate the exclude lists
		if(!rParams.mIgnoreExcludes)
		{
			rParams.mpExcludeFiles = BackupClientMakeExcludeList_Files(loc);
			rParams.mpExcludeDirs = BackupClientMakeExcludeList_Dirs(loc);
		}
				
		// Then get it compared
		Compare(std::string("/") + rLocation, 
			loc.GetKeyValue("Path"), rParams);
	}
	catch(...)
	{
		// Clean up
		rParams.DeleteExcludeLists();
		throw;
	}
	
	// Delete exclude lists
	rParams.DeleteExcludeLists();
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::Compare(const std::string &, const std::string &, BackupQueries::CompareParams &)
//		Purpose: Compare a store directory against a local directory
//		Created: 2003/10/13
//
// --------------------------------------------------------------------------
void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLocalDir, BackupQueries::CompareParams &rParams)
{
#ifdef WIN32
	std::string storeDirEncoded;
	if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return;
#else
	const std::string& storeDirEncoded(rStoreDir);
#endif
	
	// Get the directory ID of the directory -- only use current data
	int64_t dirID = FindDirectoryObjectID(storeDirEncoded);
	
	// Found?
	if(dirID == 0)
	{
		printf("Local directory '%s' exists, but "
			"server directory '%s' does not exist\n", 
			rLocalDir.c_str(), rStoreDir.c_str());		
		rParams.mDifferences ++;
		return;
	}

#ifdef WIN32
	std::string localDirEncoded;
	if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return;
#else
	std::string localDirEncoded(rLocalDir);
#endif
	
	// Go!
	Compare(dirID, storeDirEncoded, localDirEncoded, rParams);
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::Compare(int64_t, const std::string &,
//			 const std::string &, BackupQueries::CompareParams &)
//		Purpose: Compare a store directory against a local directory
//		Created: 2003/10/13
//
// --------------------------------------------------------------------------
void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const std::string &rLocalDir, BackupQueries::CompareParams &rParams)
{
#ifdef WIN32
	// By this point, rStoreDir and rLocalDir should be in UTF-8 encoding

	std::string localName;
	std::string storeName;

	if(!ConvertUtf8ToConsole(rLocalDir.c_str(), localName)) return;
	if(!ConvertUtf8ToConsole(rStoreDir.c_str(), storeName)) return;
#else
	const std::string& localName(rLocalDir);
	const std::string& storeName(rStoreDir);
#endif

	// Get info on the local directory
	struct stat st;
	if(::lstat(rLocalDir.c_str(), &st) != 0)
	{
		// What kind of error?
		if(errno == ENOTDIR)
		{
			printf("Local object '%s' is a file, "
				"server object '%s' is a directory\n", 
				localName.c_str(), storeName.c_str());
			rParams.mDifferences ++;
		}
		else if(errno == ENOENT)
		{
			printf("Local directory '%s' does not exist "
				"(compared to server directory '%s')\n",
				localName.c_str(), storeName.c_str());
		}
		else
		{
			printf("ERROR: stat on local dir '%s'\n", 
				localName.c_str());
		}
		return;
	}

	// Get the directory listing from the store
	mrConnection.QueryListDirectory(
			DirID,
			BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,	// get everything
			BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted,	// except for old versions and deleted files
			true /* want attributes */);

	// Retrieve the directory from the stream following
	BackupStoreDirectory dir;
	std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
	dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());

	// Test out the attributes
	if(!dir.HasAttributes())
	{
		printf("Store directory '%s' doesn't have attributes.\n", 
			storeName.c_str());
	}
	else
	{
		// Fetch the attributes
		const StreamableMemBlock &storeAttr(dir.GetAttributes());
		BackupClientFileAttributes attr(storeAttr);

		// Get attributes of local directory
		BackupClientFileAttributes localAttr;
		localAttr.ReadAttributes(rLocalDir.c_str(), 
			true /* directories have zero mod times */);

		if(!(attr.Compare(localAttr, true, true /* ignore modification times */)))
		{
			printf("Local directory '%s' has different attributes "
				"to store directory '%s'.\n",
				localName.c_str(), storeName.c_str());
			rParams.mDifferences ++;
		}
	}

	// Open the local directory
	DIR *dirhandle = ::opendir(rLocalDir.c_str());
	if(dirhandle == 0)
	{
		printf("ERROR: opendir on local dir '%s'\n", localName.c_str());
		return;
	}
	try
	{
		// Read the files and directories into sets
		std::set<std::string> localFiles;
		std::set<std::string> localDirs;
		struct dirent *localDirEn = 0;
		while((localDirEn = readdir(dirhandle)) != 0)
		{
			// Not . and ..!
			if(localDirEn->d_name[0] == '.' && 
				(localDirEn->d_name[1] == '\0' || (localDirEn->d_name[1] == '.' && localDirEn->d_name[2] == '\0')))
			{
				// ignore, it's . or ..
				continue;
			}

#ifndef HAVE_VALID_DIRENT_D_TYPE
			std::string fn(rLocalDir);
			fn += DIRECTORY_SEPARATOR_ASCHAR;
			fn += localDirEn->d_name;
			struct stat st;
			if(::lstat(fn.c_str(), &st) != 0)
			{
			    THROW_EXCEPTION(CommonException, OSFileError)
			}
			
			// Entry -- file or dir?
			if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
			{	
			    // File or symbolic link
			    localFiles.insert(std::string(localDirEn->d_name));
			}
			else if(S_ISDIR(st.st_mode))
			{
			    // Directory
			    localDirs.insert(std::string(localDirEn->d_name));
			}			
#else
			// Entry -- file or dir?
			if(localDirEn->d_type == DT_REG || localDirEn->d_type == DT_LNK)
			{
				// File or symbolic link
				localFiles.insert(std::string(localDirEn->d_name));
			}
			else if(localDirEn->d_type == DT_DIR)
			{
				// Directory
				localDirs.insert(std::string(localDirEn->d_name));
			}
#endif
		}
		// Close directory
		if(::closedir(dirhandle) != 0)
		{
			printf("ERROR: closedir on local dir '%s'\n", 
				localName.c_str());
		}
		dirhandle = 0;
	
		// Do the same for the store directories
		std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeFiles;
		std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeDirs;
		
		BackupStoreDirectory::Iterator i(dir);
		BackupStoreDirectory::Entry *storeDirEn = 0;
		while((storeDirEn = i.Next()) != 0)
		{
			// Decrypt filename
			BackupStoreFilenameClear name(storeDirEn->GetName());
		
			// What is it?
			if((storeDirEn->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == BackupStoreDirectory::Entry::Flags_File)
			{
				// File
				storeFiles.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn));
			}
			else
			{
				// Dir
				storeDirs.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn));
			}
		}

#ifdef _MSC_VER
		typedef std::set<std::string>::iterator string_set_iter_t;
#else
		typedef std::set<std::string>::const_iterator string_set_iter_t;
#endif
		
		// Now compare files.
		for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i)
		{
			// Does the file exist locally?
			string_set_iter_t local(localFiles.find(i->first));
			if(local == localFiles.end())
			{
				// Not found -- report
				printf("Local file '%s" DIRECTORY_SEPARATOR 
					"%s' does not exist, "
					"but store file '%s/%s' does.\n",
					localName.c_str(), i->first.c_str(), 
					storeName.c_str(), i->first.c_str());
				rParams.mDifferences ++;
			}
			else
			{
				try
				{
					// make local name of file for comparison
					std::string localName(rLocalDir + DIRECTORY_SEPARATOR + i->first);

					// Files the same flag?
					bool equal = true;
					
					// File modified after last sync flag
					bool modifiedAfterLastSync = false;
						
					if(rParams.mQuickCompare)
					{
						// Compare file -- fetch it
						mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID());

						// Stream containing block index
						std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
						
						// Compare
						equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(localName.c_str(), *blockIndexStream, mrConnection.GetTimeout());
					}
					else
					{
						// Compare file -- fetch it
						mrConnection.QueryGetFile(DirID, i->second->GetObjectID());
	
						// Stream containing encoded file
						std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
	
						// Decode it
						std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
						// Got additional attibutes?
						if(i->second->HasAttributes())
						{
							// Use these attributes
							const StreamableMemBlock &storeAttr(i->second->GetAttributes());
							BackupClientFileAttributes attr(storeAttr);
							fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout(), &attr).release());
						}
						else
						{
							// Use attributes stored in file
							fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release());
						}
						
						// Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid.
						if(!fileOnServerStream.get())
						{
							THROW_EXCEPTION(BackupStoreException, Internal)
						}
						
						// Compare attributes
						BackupClientFileAttributes localAttr;
						box_time_t fileModTime = 0;
						localAttr.ReadAttributes(localName.c_str(), false /* don't zero mod times */, &fileModTime);					
						modifiedAfterLastSync = (fileModTime > rParams.mLatestFileUploadTime);
						if(!localAttr.Compare(fileOnServerStream->GetAttributes(),
								true /* ignore attr mod time */,
								fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */))
						{
							printf("Local file '%s"
								DIRECTORY_SEPARATOR
								"%s' has different attributes "
								"to store file '%s/%s'.\n",
								localName.c_str(), i->first.c_str(), storeName.c_str(), i->first.c_str());						
							rParams.mDifferences ++;
							if(modifiedAfterLastSync)
							{
								rParams.mDifferencesExplainedByModTime ++;
								printf("(the file above was modified after the last sync time -- might be reason for difference)\n");
							}
							else if(i->second->HasAttributes())
							{
								printf("(the file above has had new attributes applied)\n");
							}
						}
	
						// Compare contents, if it's a regular file not a link
						// Remember, we MUST read the entire stream from the server.
						if(!fileOnServerStream->IsSymLink())
						{
							// Open the local file
							FileStream l(localName.c_str());
							
							// Size
							IOStream::pos_type fileSizeLocal = l.BytesLeftToRead();
							IOStream::pos_type fileSizeServer = 0;
							
							// Test the contents
							char buf1[2048];
							char buf2[2048];
							while(fileOnServerStream->StreamDataLeft() && l.StreamDataLeft())
							{
								int size = fileOnServerStream->Read(buf1, sizeof(buf1), mrConnection.GetTimeout());
								fileSizeServer += size;
								
								if(l.Read(buf2, size) != size
										|| ::memcmp(buf1, buf2, size) != 0)
								{
									equal = false;
									break;
								}
							}
	
							// Check read all the data from the server and file -- can't be equal if local and remote aren't the same length
							// Can't use StreamDataLeft() test on file, because if it's the same size, it won't know
							// it's EOF yet.
							if(fileOnServerStream->StreamDataLeft() || fileSizeServer != fileSizeLocal)
							{
								equal = false;
							}

							// Must always read the entire decoded string, if it's not a symlink
							if(fileOnServerStream->StreamDataLeft())
							{
								// Absorb all the data remaining
								char buffer[2048];
								while(fileOnServerStream->StreamDataLeft())
								{
									fileOnServerStream->Read(buffer, sizeof(buffer), mrConnection.GetTimeout());
								}
							}
						}
					}

					// Report if not equal.
					if(!equal)
					{
						printf("Local file '%s"
							DIRECTORY_SEPARATOR
							"%s' has different contents "
							"to store file '%s/%s'.\n",
							localName.c_str(), i->first.c_str(), storeName.c_str(), i->first.c_str());
						rParams.mDifferences ++;
						if(modifiedAfterLastSync)
						{
							rParams.mDifferencesExplainedByModTime ++;
							printf("(the file above was modified after the last sync time -- might be reason for difference)\n");
						}
						else if(i->second->HasAttributes())
						{
							printf("(the file above has had new attributes applied)\n");
						}
					}
				}
				catch(BoxException &e)
				{
					printf("ERROR: (%d/%d) during file fetch and comparsion for '%s/%s'\n",
						e.GetType(),
						e.GetSubType(),
						storeName.c_str(), 
						i->first.c_str());
				}
				catch(...)
				{
					printf("ERROR: (unknown) during file fetch and comparsion for '%s/%s'\n", storeName.c_str(), i->first.c_str());
				}

				// Remove from set so that we know it's been compared
				localFiles.erase(local);
			}
		}
		
		// Report any files which exist on the locally, but not on the store
		for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i)
		{
			std::string localFileName(rLocalDir + 
				DIRECTORY_SEPARATOR + *i);
			// Should this be ignored (ie is excluded)?
			if(rParams.mpExcludeFiles == 0 || 
				!(rParams.mpExcludeFiles->IsExcluded(localFileName)))
			{
				printf("Local file '%s" DIRECTORY_SEPARATOR
					"%s' exists, but store file '%s/%s' "
					"does not exist.\n",
					localName.c_str(), (*i).c_str(), 
					storeName.c_str(), (*i).c_str());
				rParams.mDifferences ++;
				
				// Check the file modification time
				{
					struct stat st;
					if(::stat(localFileName.c_str(), &st) == 0)
					{
						if(FileModificationTime(st) > rParams.mLatestFileUploadTime)
						{
							rParams.mDifferencesExplainedByModTime ++;
							printf("(the file above was modified after the last sync time -- might be reason for difference)\n");
						}
					}
				}
			}
			else
			{
				rParams.mExcludedFiles ++;
			}
		}		
		
		// Finished with the files, clear the sets to reduce memory usage slightly
		localFiles.clear();
		storeFiles.clear();
		
		// Now do the directories, recusively to check subdirectories
		for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i)
		{
			// Does the directory exist locally?
			string_set_iter_t local(localDirs.find(i->first));
			if(local == localDirs.end())
			{
				// Not found -- report
				printf("Local directory '%s" 
					DIRECTORY_SEPARATOR "%s' "
					"does not exist, but store directory "
					"'%s/%s' does.\n",
					localName.c_str(), i->first.c_str(), 
					storeName.c_str(), i->first.c_str());
				rParams.mDifferences ++;
			}
			else
			{
				// Compare directory
				Compare(i->second->GetObjectID(), rStoreDir + "/" + i->first, rLocalDir + DIRECTORY_SEPARATOR + i->first, rParams);
				
				// Remove from set so that we know it's been compared
				localDirs.erase(local);
			}
		}
		
		// Report any files which exist on the locally, but not on the store
		for(std::set<std::string>::const_iterator i = localDirs.begin(); i != localDirs.end(); ++i)
		{
			std::string localName(rLocalDir + DIRECTORY_SEPARATOR + *i);
			// Should this be ignored (ie is excluded)?
			if(rParams.mpExcludeDirs == 0 || !(rParams.mpExcludeDirs->IsExcluded(localName)))
			{
				printf("Local directory '%s/%s' exists, but "
					"store directory '%s/%s' does not exist.\n",
					localName.c_str(), (*i).c_str(), 
					storeName.c_str(), (*i).c_str());
				rParams.mDifferences ++;
			}
			else
			{
				rParams.mExcludedDirs ++;
			}
		}		
		
	}
	catch(...)
	{
		if(dirhandle != 0)
		{
			::closedir(dirhandle);
		}
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandRestore(const std::vector<std::string> &, const bool *)
//		Purpose: Restore a directory
//		Created: 23/11/03
//
// --------------------------------------------------------------------------
void BackupQueries::CommandRestore(const std::vector<std::string> &args, const bool *opts)
{
	// Check arguments
	if(args.size() != 2)
	{
		printf("Incorrect usage.\nrestore [-d] [-r] [-i] <directory-name> <local-directory-name>\n");
		return;
	}

	// Restoring deleted things?
	bool restoreDeleted = opts['d'];

	// Get directory ID
	int64_t dirID = 0;
	if(opts['i'])
	{
		// Specified as ID. 
		dirID = ::strtoll(args[0].c_str(), 0, 16);
		if(dirID == LLONG_MIN || dirID == LLONG_MAX || dirID == 0)
		{
			printf("Not a valid object ID (specified in hex)\n");
			return;
		}
	}
	else
	{
#ifdef WIN32
		std::string storeDirEncoded;
		if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded))
			return;
#else
		const std::string& storeDirEncoded(args[0]);
#endif
	
		// Look up directory ID
		dirID = FindDirectoryObjectID(storeDirEncoded, 
			false /* no old versions */, 
			restoreDeleted /* find deleted dirs */);
	}
	
	// Allowable?
	if(dirID == 0)
	{
		printf("Directory '%s' not found on server\n", args[0].c_str());
		return;
	}
	if(dirID == BackupProtocolClientListDirectory::RootDirectory)
	{
		printf("Cannot restore the root directory -- restore locations individually.\n");
		return;
	}
	
#ifdef WIN32
	std::string localName;
	if(!ConvertConsoleToUtf8(args[1].c_str(), localName)) return;
#else
	std::string localName(args[1]);
#endif

	// Go and restore...
	switch(BackupClientRestore(mrConnection, dirID, localName.c_str(), 
		true /* print progress dots */, restoreDeleted, 
		false /* don't undelete after restore! */, 
		opts['r'] /* resume? */))
	{
	case Restore_Complete:
		printf("Restore complete\n");
		break;
	
	case Restore_ResumePossible:
		printf("Resume possible -- repeat command with -r flag to resume\n");
		break;
	
	case Restore_TargetExists:
		printf("The target directory exists. You cannot restore over an existing directory.\n");
		break;
		
	default:
		printf("ERROR: Unknown restore result.\n");
		break;
	}
}



// These are autogenerated by a script.
extern char *help_commands[];
extern char *help_text[];


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandHelp(const std::vector<std::string> &args)
//		Purpose: Display help on commands
//		Created: 15/2/04
//
// --------------------------------------------------------------------------
void BackupQueries::CommandHelp(const std::vector<std::string> &args)
{
	if(args.size() == 0)
	{
		// Display a list of all commands
		printf("Available commands are:\n");
		for(int c = 0; help_commands[c] != 0; ++c)
		{
			printf("    %s\n", help_commands[c]);
		}
		printf("Type \"help <command>\" for more information on a command.\n\n");
	}
	else
	{
		// Display help on a particular command
		int c;
		for(c = 0; help_commands[c] != 0; ++c)
		{
			if(::strcmp(help_commands[c], args[0].c_str()) == 0)
			{
				// Found the command, print help
				printf("\n%s\n", help_text[c]);
				break;
			}
		}
		if(help_commands[c] == 0)
		{
			printf("No help found for command '%s'\n", args[0].c_str());
		}
	}
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandUsage()
//		Purpose: Display storage space used on server
//		Created: 19/4/04
//
// --------------------------------------------------------------------------
void BackupQueries::CommandUsage()
{
	// Request full details from the server
	std::auto_ptr<BackupProtocolClientAccountUsage> usage(mrConnection.QueryGetAccountUsage());

	// Display each entry in turn
	int64_t hardLimit = usage->GetBlocksHardLimit();
	int32_t blockSize = usage->GetBlockSize();
	CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, blockSize);
	CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), hardLimit, blockSize);
	CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), hardLimit, blockSize);
	CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), hardLimit, blockSize);
	CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), hardLimit, blockSize);
	CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize);
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandUsageDisplayEntry(const char *, int64_t, int64_t, int32_t)
//		Purpose: Display an entry in the usage table
//		Created: 19/4/04
//
// --------------------------------------------------------------------------
void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, int64_t HardLimit, int32_t BlockSize)
{
	// Calculate size in Mb
	double mb = (((double)Size) * ((double)BlockSize)) / ((double)(1024*1024));
	int64_t percent = (Size * 100) / HardLimit;

	// Bar graph
	char bar[41];
	unsigned int b = (int)((Size * (sizeof(bar)-1)) / HardLimit);
	if(b > sizeof(bar)-1) {b = sizeof(bar)-1;}
	for(unsigned int l = 0; l < b; l++)
	{
		bar[l] = '*';
	}
	bar[b] = '\0';

	// Print the entryj
	::printf("%14s %10.1fMb %3d%% %s\n", Name, mb, (int32_t)percent, bar);
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    BackupQueries::CommandUndelete(const std::vector<std::string> &, const bool *)
//		Purpose: Undelete a directory
//		Created: 23/11/03
//
// --------------------------------------------------------------------------
void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const bool *opts)
{
	// Check arguments
	if(args.size() != 1)
	{
		printf("Incorrect usage.\nundelete <directory-name>\n");
		return;
	}

#ifdef WIN32
	std::string storeDirEncoded;
	if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return;
#else
	const std::string& storeDirEncoded(args[0]);
#endif
	
	// Get directory ID
	int64_t dirID = FindDirectoryObjectID(storeDirEncoded, 
		false /* no old versions */, true /* find deleted dirs */);
	
	// Allowable?
	if(dirID == 0)
	{
		printf("Directory '%s' not found on server\n", args[0].c_str());
		return;
	}
	if(dirID == BackupProtocolClientListDirectory::RootDirectory)
	{
		printf("Cannot undelete the root directory.\n");
		return;
	}

	// Undelete
	mrConnection.QueryUndeleteDirectory(dirID);
}


syntax highlighted by Code2HTML, v. 0.9.1