/*
 * Copyright 2013-2014, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Ingo Weinhold <ingo_weinhold@gmx.de>
 */
 
 
#include "Root.h"
 
#include <Alert.h>
#include <Directory.h>
#include <Entry.h>
#include <package/CommitTransactionResult.h>
#include <package/PackageDefs.h>
#include <Path.h>
 
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <Server.h>
 
#include <package/DaemonDefs.h>
#include <package/manager/Exceptions.h>
 
#include "Constants.h"
#include "DebugSupport.h"
#include "PackageManager.h"
 
 
using namespace BPackageKit::BPrivate;
using namespace BPackageKit::BManager::BPrivate;
 
 
// #pragma mark - AbstractVolumeJob
 
 
struct Root::AbstractVolumeJob : public Job {
	AbstractVolumeJob(Volume* volume)
		:
		fVolume(volume)
	{
	}
 
	Volume* GetVolume() const
	{
		return fVolume;
	}
 
protected:
	Volume*	fVolume;
};
 
 
// #pragma mark - VolumeJob
 
 
struct Root::VolumeJob : public AbstractVolumeJob {
	VolumeJob(Volume* volume, void (Root::*method)(Volume*))
		:
		AbstractVolumeJob(volume),
		fMethod(method)
	{
	}
 
	virtual void Do()
	{
		(fVolume->GetRoot()->*fMethod)(fVolume);
	}
 
private:
	void	(Root::*fMethod)(Volume*);
};
 
 
// #pragma mark - ProcessNodeMonitorEventsJob
 
 
struct Root::ProcessNodeMonitorEventsJob : public VolumeJob {
	ProcessNodeMonitorEventsJob(Volume* volume, void (Root::*method)(Volume*))
		:
		VolumeJob(volume, method)
	{
		fVolume->PackageJobPending();
	}
 
	~ProcessNodeMonitorEventsJob()
	{
		fVolume->PackageJobFinished();
	}
};
 
 
// #pragma mark - CommitTransactionJob
 
 
struct Root::CommitTransactionJob : public AbstractVolumeJob {
	CommitTransactionJob(Root* root, Volume* volume, BMessage* message)
		:
		AbstractVolumeJob(volume),
		fRoot(root),
		fMessage(message)
	{
		fVolume->PackageJobPending();
	}
 
	~CommitTransactionJob()
	{
		fVolume->PackageJobFinished();
	}
 
	virtual void Do()
	{
		fRoot->_CommitTransaction(fVolume, fMessage.Get());
	}
 
private:
	Root*					fRoot;
	ObjectDeleter<BMessage>	fMessage;
};
 
 
// #pragma mark - VolumeJobFilter
 
 
struct Root::VolumeJobFilter : public ::JobQueue::Filter {
	VolumeJobFilter(Volume* volume)
		:
		fVolume(volume)
	{
	}
 
	virtual bool FilterJob(Job* job)
	{
		AbstractVolumeJob* volumeJob = dynamic_cast<AbstractVolumeJob*>(job);
		return volumeJob != NULL && volumeJob->GetVolume() == fVolume;
	}
 
private:
	Volume*	fVolume;
};
 
 
// #pragma mark - Root
 
 
Root::Root()
	:
	fLock("packagefs root"),
	fNodeRef(),
	fIsSystemRoot(false),
	fPath(),
	fSystemVolume(NULL),
	fHomeVolume(NULL),
	fJobQueue(),
	fJobRunner(-1)
{
}
 
 
Root::~Root()
{
	fJobQueue.Close();
 
	if (fJobRunner >= 0)
		wait_for_thread(fJobRunner, NULL);
}
 
 
status_t
Root::Init(const node_ref& nodeRef, bool isSystemRoot)
{
	fNodeRef = nodeRef;
	fIsSystemRoot = isSystemRoot;
 
	// init members and spawn job runner thread
	status_t error = fJobQueue.Init();
	if (error != B_OK)
		RETURN_ERROR(error);
 
	error = fLock.InitCheck();
	if (error != B_OK)
		RETURN_ERROR(error);
 
	fJobRunner = spawn_thread(&_JobRunnerEntry, "job runner", B_NORMAL_PRIORITY,
		this);
	if (fJobRunner < 0)
		RETURN_ERROR(fJobRunner);
 
	// get the path
	BDirectory directory;
	error = directory.SetTo(&fNodeRef);
	if (error != B_OK) {
		ERROR("Root::Init(): failed to open directory: %s\n", strerror(error));
		RETURN_ERROR(error);
	}
 
	BEntry entry;
	error = directory.GetEntry(&entry);
 
	BPath path;
	if (error == B_OK)
		error = entry.GetPath(&path);
 
	if (error != B_OK) {
		ERROR("Root::Init(): failed to get directory path: %s\n",
			strerror(error));
		RETURN_ERROR(error);
	}
 
	fPath = path.Path();
	if (fPath.IsEmpty())
		RETURN_ERROR(B_NO_MEMORY);
 
	resume_thread(fJobRunner);
 
	return B_OK;
}
 
 
status_t
Root::RegisterVolume(Volume* volume)
{
	AutoLocker<BLocker> locker(fLock);
 
	Volume** volumeToSet = _GetVolume(volume->MountType());
	if (volumeToSet == NULL)
		return B_BAD_VALUE;
 
	if (*volumeToSet != NULL) {
		ERROR("Root::RegisterVolume(): can't register volume at \"%s\", since "
			"there's already volume at \"%s\" with the same type.\n",
			volume->Path().String(), (*volumeToSet)->Path().String());
		return B_BAD_VALUE;
	}
 
	*volumeToSet = volume;
	volume->SetRoot(this);
 
	// queue a job for reading the volume's packages
	status_t error = _QueueJob(
		new(std::nothrow) VolumeJob(volume, &Root::_InitPackages));
	if (error != B_OK) {
		volume->SetRoot(NULL);
		*volumeToSet = NULL;
		return error;
	}
 
	return B_OK;
}
 
 
void
Root::UnregisterVolume(Volume* volume)
{
	AutoLocker<BLocker> locker(fLock);
 
	Volume** volumeToSet = _GetVolume(volume->MountType());
	if (volumeToSet == NULL || *volumeToSet != volume) {
		ERROR("Root::UnregisterVolume(): can't unregister unknown volume at "
			"\"%s.\n", volume->Path().String());
		return;
	}
 
	*volumeToSet = NULL;
 
	// Use the job queue to delete the volume to make sure there aren't any
	// pending jobs that reference the volume.
	_QueueJob(new(std::nothrow) VolumeJob(volume, &Root::_DeleteVolume));
}
 
 
Volume*
Root::FindVolume(dev_t deviceID) const
{
	AutoLocker<BLocker> locker(fLock);
 
	Volume* volumes[] = { fSystemVolume, fHomeVolume };
	for (size_t i = 0; i < sizeof(volumes) / sizeof(volumes[0]); i++) {
		Volume* volume = volumes[i];
		if (volume != NULL && volume->DeviceID() == deviceID)
			return volume;
	}
 
	return NULL;
}
 
 
Volume*
Root::GetVolume(BPackageInstallationLocation location)
{
	switch ((BPackageInstallationLocation)location) {
		case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM:
			return fSystemVolume;
		case B_PACKAGE_INSTALLATION_LOCATION_HOME:
			return fHomeVolume;
		default:
			return NULL;
	}
}
 
 
void
Root::HandleRequest(BMessage* message)
{
	ObjectDeleter<BMessage> messageDeleter(message);
 
	// get the location and the volume
	int32 location;
	if (message->FindInt32("location", &location) != B_OK
		|| location < 0
		|| location >= B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT) {
		return;
	}
 
	AutoLocker<BLocker> locker(fLock);
 
	Volume* volume = GetVolume((BPackageInstallationLocation)location);
	if (volume == NULL)
		return;
 
	switch (message->what) {
		case B_MESSAGE_GET_INSTALLATION_LOCATION_INFO:
			volume->HandleGetLocationInfoRequest(message);
			break;
 
		case B_MESSAGE_COMMIT_TRANSACTION:
		{
			// The B_MESSAGE_COMMIT_TRANSACTION request must be handled in the
			// job thread. But only queue a job, if there aren't package jobs
			// pending already.
			if (volume->IsPackageJobPending()) {
				BMessage reply(B_MESSAGE_COMMIT_TRANSACTION_REPLY);
				BCommitTransactionResult result(
					B_TRANSACTION_INSTALLATION_LOCATION_BUSY);
				if (result.AddToMessage(reply) == B_OK) {
					message->SendReply(&reply, (BHandler*)NULL,
						kCommunicationTimeout);
				}
				return;
			}
 
			CommitTransactionJob* job = new(std::nothrow) CommitTransactionJob(
				this, volume, message);
			if (job == NULL)
				return;
 
			messageDeleter.Detach();
 
			_QueueJob(job);
			break;
		}
 
		default:
			break;
	}
}
 
 
void
Root::VolumeNodeMonitorEventOccurred(Volume* volume)
{
	_QueueJob(new(std::nothrow) ProcessNodeMonitorEventsJob(volume,
		&Root::_ProcessNodeMonitorEvents));
}
 
 
void
Root::LastReferenceReleased()
{
}
 
 
Volume**
Root::_GetVolume(PackageFSMountType mountType)
{
	switch (mountType) {
		case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
			return &fSystemVolume;
		case PACKAGE_FS_MOUNT_TYPE_HOME:
			return &fHomeVolume;
		case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
		default:
			return NULL;
	}
}
 
 
Volume*
Root::_NextVolumeFor(Volume* volume)
{
	if (volume == NULL)
		return NULL;
 
	PackageFSMountType mountType = volume->MountType();
 
	do {
		switch (mountType) {
			case PACKAGE_FS_MOUNT_TYPE_HOME:
				mountType = PACKAGE_FS_MOUNT_TYPE_SYSTEM;
				break;
			case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
			case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
			default:
				return NULL;
		}
 
		volume = *_GetVolume(mountType);
	} while (volume == NULL);
 
	return volume;
}
 
 
void
Root::_InitPackages(Volume* volume)
{
	if (volume->InitPackages(this) == B_OK) {
		AutoLocker<BLocker> locker(fLock);
		Volume* nextVolume = _NextVolumeFor(volume);
		Volume* nextNextVolume = _NextVolumeFor(nextVolume);
		locker.Unlock();
 
		volume->InitialVerify(nextVolume, nextNextVolume);
	}
}
 
 
void
Root::_DeleteVolume(Volume* volume)
{
	// delete all pending jobs for that volume
	VolumeJobFilter filter(volume);
	fJobQueue.DeleteJobs(&filter);
 
	delete volume;
}
 
 
void
Root::_ProcessNodeMonitorEvents(Volume* volume)
{
	volume->ProcessPendingNodeMonitorEvents();
 
	if (!volume->HasPendingPackageActivationChanges())
		return;
 
	// If this is not the system root, just activate/deactivate the packages.
	if (!fIsSystemRoot) {
		volume->ProcessPendingPackageActivationChanges();
		return;
	}
 
	// For the system root do the full dependency analysis.
 
	PRINT("Root::_ProcessNodeMonitorEvents(): running package manager...\n");
	try {
		PackageManager packageManager(this, volume);
		packageManager.HandleUserChanges();
	} catch (BNothingToDoException&) {
		PRINT("Root::_ProcessNodeMonitorEvents(): -> nothing to do\n");
	} catch (std::bad_alloc&) {
		_ShowError(
			"Insufficient memory while trying to apply package changes.");
	} catch (BFatalErrorException& exception) {
		if (exception.Error() == B_OK) {
			_ShowError(exception.Message());
		} else {
			_ShowError(BString().SetToFormat("%s: %s",
				exception.Message().String(), strerror(exception.Error())));
		}
		// TODO: Print exception.Details()?
	} catch (BAbortedByUserException&) {
		PRINT("Root::_ProcessNodeMonitorEvents(): -> aborted by user\n");
	}
 
	volume->ClearPackageActivationChanges();
}
 
 
void
Root::_CommitTransaction(Volume* volume, BMessage* message)
{
	volume->HandleCommitTransactionRequest(message);
}
 
 
status_t
Root::_QueueJob(Job* job)
{
	if (job == NULL)
		return B_NO_MEMORY;
 
	BReference<Job> jobReference(job, true);
	if (!fJobQueue.QueueJob(job)) {
		// job queue already closed
		return B_BAD_VALUE;
	}
 
	return B_OK;
}
 
 
/*static*/ status_t
Root::_JobRunnerEntry(void* data)
{
	return ((Root*)data)->_JobRunner();
}
 
 
status_t
Root::_JobRunner()
{
	while (Job* job = fJobQueue.DequeueJob()) {
		job->Do();
		job->ReleaseReference();
	}
 
	return B_OK;
}
 
 
/*static*/ void
Root::_ShowError(const char* errorMessage)
{
	BServer* server = dynamic_cast<BServer*>(be_app);
	if (server != NULL && server->InitGUIContext() == B_OK) {
		BAlert* alert = new(std::nothrow) BAlert("Package error",
			errorMessage, "OK", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
		if (alert != NULL) {
			alert->SetShortcut(0, B_ESCAPE);
			alert->Go();
			return;
		}
	}
 
	ERROR("Root::_ShowError(): %s\n", errorMessage);
}

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.