
void CSandMan::OnFileToRecover(const QString& BoxName, const QString& FilePath, const QString& BoxPath, quint32 ProcessId)
{
	CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
	if ((!pBox.isNull() && pBox.objectCast<CSandBoxPlus>()->IsRecoverySuspended()) || IsDisableRecovery())
		return;

	if (theConf->GetBool("Options/InstantRecovery", true))
	{
		CRecoveryWindow* pWnd = ShowRecovery(pBox, false);

		//if (!theConf->GetBool("Options/AlwaysOnTop", false)) {
		//	SetWindowPos((HWND)pWnd->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
		//	QTimer::singleShot(100, this, [pWnd]() {
		//		SetWindowPos((HWND)pWnd->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
		//		});
		//}

		if (pWnd)
			pWnd->AddFile(FilePath, BoxPath);
	}
	else
		m_pPopUpWindow->AddFileToRecover(FilePath, BoxPath, pBox, ProcessId);
}

bool CSandMan::OpenRecovery(const CSandBoxPtr& pBox, bool& DeleteSnapshots, bool bCloseEmpty)
{
	auto pBoxEx = pBox.objectCast<CSandBoxPlus>();
	if (!pBoxEx) return false;
	if (pBoxEx->m_pRecoveryWnd != NULL) {
		if (pBoxEx->m_pRecoveryWnd->IsDeleteDialog())
			return false;
		pBoxEx->m_pRecoveryWnd->close();
	}

	CRecoveryWindow* pRecoveryWnd = pBoxEx->m_pRecoveryWnd = new CRecoveryWindow(pBox, false, this);
	connect(this, SIGNAL(Closed()), pBoxEx->m_pRecoveryWnd, SLOT(close()));
	if (pBoxEx->m_pRecoveryWnd->FindFiles() == 0 && bCloseEmpty) {
		delete pBoxEx->m_pRecoveryWnd;
		pBoxEx->m_pRecoveryWnd = NULL;
		return true;
	}
	else {
		connect(pBoxEx->m_pRecoveryWnd, &CRecoveryWindow::Closed, [pBoxEx]() {
			pBoxEx->m_pRecoveryWnd = NULL;
		});
		if (pBoxEx->m_pRecoveryWnd->exec() != 1)
			return false;
	}
	DeleteSnapshots = pRecoveryWnd->IsDeleteSnapshots();
	return true;
}

CRecoveryWindow* CSandMan::ShowRecovery(const CSandBoxPtr& pBox, bool bFind)
{
	auto pBoxEx = pBox.objectCast<CSandBoxPlus>();
	if (!pBoxEx) return NULL;
	if (pBoxEx->m_pRecoveryWnd == NULL) {
		pBoxEx->m_pRecoveryWnd = new CRecoveryWindow(pBox, bFind == false);
		connect(this, SIGNAL(Closed()), pBoxEx->m_pRecoveryWnd, SLOT(close()));
		connect(pBoxEx->m_pRecoveryWnd, &CRecoveryWindow::Closed, [pBoxEx]() {
			pBoxEx->m_pRecoveryWnd = NULL;
		});
		pBoxEx->m_pRecoveryWnd->show();
	}
	else if(bFind) { // We don't want to force window in front on instant recovery 
		pBoxEx->m_pRecoveryWnd->setWindowState((pBoxEx->m_pRecoveryWnd->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
		SetForegroundWindow((HWND)pBoxEx->m_pRecoveryWnd->winId());
	}
	if(bFind)
		pBoxEx->m_pRecoveryWnd->FindFiles();
	return pBoxEx->m_pRecoveryWnd;
}

SB_PROGRESS CSandMan::CheckFiles(const QString& BoxName, const QStringList& Files)
{
	CSbieProgressPtr pProgress = CSbieProgressPtr(new CSbieProgress());
	CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
	QStringList Checkers;
	if (!pBox.isNull()) {
		foreach(const QString & Value, pBox->GetTextList("OnFileRecovery", true, false, true)) {
			Checkers.append(pBox->Expand(Value));
		}
	}
	QtConcurrent::run(CSandMan::CheckFilesAsync, pProgress, BoxName, Files, Checkers);
	return SB_PROGRESS(OP_ASYNC, pProgress);
}

void CSandMan::CheckFilesAsync(const CSbieProgressPtr& pProgress, const QString& BoxName, const QStringList& Files, const QStringList& Checkers)
{
	int FailCount = 0;
	for (QStringList::const_iterator I = Files.begin(); I != Files.end(); ++I) 
	{
		if (pProgress->IsCanceled()) break;

		QString BoxPath = *I;
		QString FileName = BoxPath.mid(BoxPath.lastIndexOf("\\") + 1);
		
		pProgress->ShowMessage(tr("Checking file %1").arg(FileName));

		foreach(const QString & Value, Checkers) {
			QString Output;
			int ret = CSbieUtils::ExecCommandEx(Value + " \"" + BoxPath + "\"", &Output, 15000); // 15 sec timeout
			if (ret != 0) {
				FailCount++;
				QMetaObject::invokeMethod(theGUI, "ShowMessage", Qt::BlockingQueuedConnection, // show this message using the GUI thread
					Q_ARG(QString, tr("The file %1 failed a security check!\r\n\r\n%2").arg(BoxPath).arg(Output)),
					Q_ARG(int, QMessageBox::Warning)
				);
			}
		}
	}
	if (FailCount == 0) {
		QMetaObject::invokeMethod(theGUI, "ShowMessage", Qt::BlockingQueuedConnection, // show this message using the GUI thread
			Q_ARG(QString, tr("All files passed the checks")),
			Q_ARG(int, QMessageBox::Information)
		);
	}

	pProgress->Finish(SB_OK);
}

SB_PROGRESS CSandMan::RecoverFiles(const QString& BoxName, const QList<QPair<QString, QString>>& FileList, int Action)
{
	CSbieProgressPtr pProgress = CSbieProgressPtr(new CSbieProgress());
	CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
	QStringList Checkers;
	if (!pBox.isNull()) {
		foreach(const QString & Value, pBox->GetTextList("OnFileRecovery", true, false, true)) {
			Checkers.append(pBox->Expand(Value));
		}
	}
	QtConcurrent::run(CSandMan::RecoverFilesAsync, pProgress, BoxName, FileList, Checkers, Action);
	return SB_PROGRESS(OP_ASYNC, pProgress);
}

void CSandMan::RecoverFilesAsync(const CSbieProgressPtr& pProgress, const QString& BoxName, const QList<QPair<QString, QString>>& FileList, const QStringList& Checkers, int Action)
{
	SB_STATUS Status = SB_OK;

	int OverwriteOnExist = -1;
	int RecoverCheckFailed = -1;

	QStringList Unrecovered;
	for (QList<QPair<QString, QString>>::const_iterator I = FileList.begin(); I != FileList.end(); ++I)
	{
		if (pProgress->IsCanceled()) break;

		QString BoxPath = I->first;
		QString RecoveryPath = I->second;
		QString FileName = BoxPath.mid(BoxPath.lastIndexOf("\\") + 1);
		QString RecoveryFolder = RecoveryPath.left(RecoveryPath.lastIndexOf("\\") + 1);

		if (!Checkers.isEmpty()) {

			pProgress->ShowMessage(tr("Checking file %1").arg(FileName));

			//bool bNoGui = true;
			//if (GetKeyState(VK_CONTROL) & 0x8000)
			//	bNoGui = false;

			int ret = 0;
			foreach(const QString & Value, Checkers) {
				QString Output;
				ret = CSbieUtils::ExecCommandEx(Value + " \"" + BoxPath + "\"", &Output, 15000); // 15 sec timeout
				if (ret != 0) {

					int Recover = RecoverCheckFailed;
					if (Recover == -1)
					{
						bool forAll = false;
						int retVal = 0;
						QMetaObject::invokeMethod(theGUI, "ShowQuestion", Qt::BlockingQueuedConnection, // show this question using the GUI thread
							Q_RETURN_ARG(int, retVal),
							Q_ARG(QString, tr("The file %1 failed a security check, do you want to recover it anyway?\r\n\r\n%2").arg(BoxPath).arg(Output)),
							Q_ARG(QString, tr("Do this for all files!")),
							Q_ARG(bool*, &forAll),
							Q_ARG(int, QDialogButtonBox::Yes | QDialogButtonBox::No),
							Q_ARG(int, QDialogButtonBox::No),
							Q_ARG(int, QMessageBox::Warning)
						);

						Recover = retVal == QDialogButtonBox::Yes ? 1 : 0;
						if (forAll)
							RecoverCheckFailed = Recover;
					}

					if (Recover == 1)
						ret = 0;
					else
						break;
				}
			}
			if (ret != 0)
				continue; // Do not recover this file
		}

		pProgress->ShowMessage(tr("Recovering file %1 to %2").arg(FileName).arg(RecoveryFolder));

		QDir().mkpath(RecoveryFolder);
		if (QFile::exists(RecoveryPath)) 
		{
			int Overwrite = OverwriteOnExist;
			if (Overwrite == -1)
			{
				bool forAll = false;
				int retVal = 0;
				QMetaObject::invokeMethod(theGUI, "ShowQuestion", Qt::BlockingQueuedConnection, // show this question using the GUI thread
					Q_RETURN_ARG(int, retVal),
					Q_ARG(QString, tr("The file %1 already exists, do you want to overwrite it?").arg(RecoveryPath)),
					Q_ARG(QString, tr("Do this for all files!")),
					Q_ARG(bool*, &forAll),
					Q_ARG(int, QDialogButtonBox::Yes | QDialogButtonBox::No),
					Q_ARG(int, QDialogButtonBox::No),
					Q_ARG(int, QMessageBox::Question)
				);

				Overwrite = retVal == QDialogButtonBox::Yes ? 1 : 0;
				if (forAll)
					OverwriteOnExist = Overwrite;
			}
			if (Overwrite == 1)
				QFile::remove(RecoveryPath);
		}

		if (!QFile::rename(BoxPath, RecoveryPath))
			Unrecovered.append(BoxPath);
		else {
			QMetaObject::invokeMethod(theGUI, "OnFileRecovered", Qt::BlockingQueuedConnection, // show this question using the GUI thread
				Q_ARG(QString, BoxName),
				Q_ARG(QString, RecoveryPath),
				Q_ARG(QString, BoxPath)
			);
		}
	}

	if (!Unrecovered.isEmpty())
		Status = SB_ERR(SB_Message, QVariantList () << (tr("Failed to recover some files: \n") + Unrecovered.join("\n")));
	else if(FileList.count() == 1 && Action != 0)
	{
		std::wstring path = FileList.first().second.toStdWString();
		switch (Action)
		{
		case 1: // open
			ShellExecute(NULL, NULL, path.c_str(), NULL, NULL, SW_SHOWNORMAL);
			break;
		case 2: // explore
			ShellExecute(NULL, NULL, L"explorer.exe", (L"/select,\"" + path + L"\"").c_str(), NULL, SW_SHOWNORMAL);
			break;
		}
	}


	pProgress->Finish(Status);
}

void CSandMan::AddFileRecovered(const QString& BoxName, const QString& FilePath)
{
	CPanelWidgetEx* pRecoveryLog = m_pRecoveryLog;
	if (pRecoveryLog == NULL) {
		pRecoveryLog = m_pRecoveryLogWnd->m_pRecoveryLog;
		if (!pRecoveryLog) return;
	}

	QTreeWidgetItem* pItem = new QTreeWidgetItem(); // Time|Box|FilePath
	pItem->setText(0, QDateTime::currentDateTime().toString("hh:mm:ss.zzz"));
	pItem->setText(1, BoxName);
	pItem->setText(2, FilePath);
	pRecoveryLog->GetTree()->addTopLevelItem(pItem);

	pRecoveryLog->GetView()->verticalScrollBar()->setValue(pRecoveryLog->GetView()->verticalScrollBar()->maximum());
}

void CSandMan::OnFileRecovered(const QString& BoxName, const QString& FilePath, const QString& BoxPath)
{
	AddFileRecovered(BoxName, FilePath);

	CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
	if (pBox)
		pBox.objectCast<CSandBoxPlus>()->UpdateSize();
}


////////////////////////////////////////////////////////////////////////////////////////
// CRecoveryLogWnd

CRecoveryLogWnd::CRecoveryLogWnd(QWidget *parent)
	: QDialog(parent)
{
	Qt::WindowFlags flags = windowFlags();
	flags |= Qt::CustomizeWindowHint;
	//flags &= ~Qt::WindowContextHelpButtonHint;
	//flags &= ~Qt::WindowSystemMenuHint;
	//flags &= ~Qt::WindowMinMaxButtonsHint;
	//flags |= Qt::WindowMinimizeButtonHint;
	//flags &= ~Qt::WindowCloseButtonHint;
	flags &= ~Qt::WindowContextHelpButtonHint;
	//flags &= ~Qt::WindowSystemMenuHint;
	setWindowFlags(flags);

	this->setWindowTitle(tr("Sandboxie-Plus - Recovery Log"));

	QGridLayout* pLayout = new QGridLayout();
	//pLayout->setContentsMargins(3,3,3,3);
	
	m_pRecoveryLog = new CPanelWidgetEx();
	m_pRecoveryLog->GetTree()->setItemDelegate(new CTreeItemDelegate());

	m_pRecoveryLog->GetTree()->setAlternatingRowColors(theConf->GetBool("Options/AltRowColors", false));

	//m_pRecoveryLog->GetView()->setItemDelegate(theGUI->GetItemDelegate());
	((QTreeWidgetEx*)m_pRecoveryLog->GetView())->setHeaderLabels(tr("Time|Box Name|File Path").split("|"));

	QAction* pAction = new QAction(tr("Cleanup Recovery Log"));
	connect(pAction, SIGNAL(triggered()), m_pRecoveryLog->GetTree(), SLOT(clear()));
	m_pRecoveryLog->GetMenu()->insertAction(m_pRecoveryLog->GetMenu()->actions()[0], pAction);
	m_pRecoveryLog->GetMenu()->insertSeparator(m_pRecoveryLog->GetMenu()->actions()[0]);

	m_pRecoveryLog->GetView()->setSelectionMode(QAbstractItemView::ExtendedSelection);
	m_pRecoveryLog->GetView()->setSortingEnabled(false);

	connect(m_pRecoveryLog->GetTree(), SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnDblClick(QTreeWidgetItem*)));

	pLayout->addWidget(new QLabel(tr("The following files were recently recovered and moved out of a sandbox.")), 0, 0);
	pLayout->addWidget(m_pRecoveryLog, 1, 0);
	this->setLayout(pLayout);

	restoreGeometry(theConf->GetBlob("RecoveryLogWindow/Window_Geometry"));
}

CRecoveryLogWnd::~CRecoveryLogWnd()
{
	theConf->SetBlob("RecoveryLogWindow/Window_Geometry", saveGeometry());
}

void CRecoveryLogWnd::closeEvent(QCloseEvent *e)
{
	emit Closed();
	//this->deleteLater();
}

void CRecoveryLogWnd::OnDblClick(QTreeWidgetItem* pItem)
{
	ShellExecute(NULL, NULL, L"explorer.exe", (L"/select,\"" + pItem->text(2).toStdWString() + L"\"").c_str(), NULL, SW_SHOWNORMAL);
}

void CSandMan::OnRecoveryLog()
{
	if (!m_pRecoveryLogWnd->isVisible()) {
		bool bAlwaysOnTop = theConf->GetBool("Options/AlwaysOnTop", false);
		m_pRecoveryLogWnd->setWindowFlag(Qt::WindowStaysOnTopHint, bAlwaysOnTop);
		SafeShow(m_pRecoveryLogWnd);
	}
}
