root/src/kits/package/manager/PackageManager.cpp
/*
 * Copyright 2013-2020, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ingo Weinhold <ingo_weinhold@gmx.de>
 *              Rene Gollent <rene@gollent.com>
 */


#include <package/manager/PackageManager.h>

#include <glob.h>

#include <Catalog.h>
#include <Directory.h>
#include <package/CommitTransactionResult.h>
#include <package/DownloadFileRequest.h>
#include <package/PackageRoster.h>
#include <package/RefreshRepositoryRequest.h>
#include <package/RepositoryCache.h>
#include <package/solver/SolverPackage.h>
#include <package/solver/SolverPackageSpecifier.h>
#include <package/solver/SolverPackageSpecifierList.h>
#include <package/solver/SolverProblem.h>
#include <package/solver/SolverProblemSolution.h>
#include <package/solver/SolverResult.h>

#include <CopyEngine.h>
#include <package/ActivationTransaction.h>
#include <package/DaemonClient.h>
#include <package/manager/RepositoryBuilder.h>

#include "FetchFileJob.h"
#include "FetchUtils.h"
#include "PackageManagerUtils.h"
#include "ValidateChecksumJob.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PackageManagerKit"


using BPackageKit::BPrivate::FetchUtils;
using BPackageKit::BPrivate::FetchFileJob;
using BPackageKit::BPrivate::ValidateChecksumJob;


namespace BPackageKit {

namespace BManager {

namespace BPrivate {


// #pragma mark - BPackageManager


BPackageManager::BPackageManager(BPackageInstallationLocation location,
        InstallationInterface* installationInterface,
        UserInteractionHandler* userInteractionHandler)
        :
        fDebugLevel(0),
        fLocation(location),
        fSolver(NULL),
        fSystemRepository(new (std::nothrow) InstalledRepository("system",
                B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, -1)),
        fHomeRepository(new (std::nothrow) InstalledRepository("home",
                B_PACKAGE_INSTALLATION_LOCATION_HOME, -3)),
        fInstalledRepositories(10),
        fOtherRepositories(10),
        fLocalRepository(new (std::nothrow) MiscLocalRepository),
        fTransactions(5),
        fInstallationInterface(installationInterface),
        fUserInteractionHandler(userInteractionHandler)
{
}


BPackageManager::~BPackageManager()
{
        delete fSolver;
        delete fSystemRepository;
        delete fHomeRepository;
        delete fLocalRepository;
}


void
BPackageManager::Init(uint32 flags)
{
        if (fSolver != NULL)
                return;

        // create the solver
        status_t error = BSolver::Create(fSolver);
        if (error != B_OK)
                DIE(error, "Failed to create solver");

        if (fSystemRepository == NULL || fHomeRepository == NULL
                || fLocalRepository == NULL) {
                throw std::bad_alloc();
        }

        fSolver->SetDebugLevel(fDebugLevel);

        BRepositoryBuilder(*fLocalRepository).AddToSolver(fSolver, false);

        // add installation location repositories
        if ((flags & B_ADD_INSTALLED_REPOSITORIES) != 0) {
                // We add only the repository of our actual installation location as the
                // "installed" repository. The repositories for the more general
                // installation locations are added as regular repositories, but with
                // better priorities than the actual (remote) repositories. This
                // prevents the solver from showing conflicts when a package in a more
                // specific installation location overrides a package in a more general
                // one. Instead any requirement that is already installed in a more
                // general installation location will turn up as to be installed as
                // well. But we can easily filter those out.
                _AddInstalledRepository(fSystemRepository);

                if (!fSystemRepository->IsInstalled()) {
                        // Only add the home repository if the directory exists
                        BPath path;
                        status_t error = find_directory(B_USER_PACKAGES_DIRECTORY, &path);
                        if (error == B_OK && BEntry(path.Path()).Exists())
                                _AddInstalledRepository(fHomeRepository);
                }
        }

        // add other repositories
        if ((flags & B_ADD_REMOTE_REPOSITORIES) != 0) {
                BPackageRoster roster;
                BStringList repositoryNames;
                error = roster.GetRepositoryNames(repositoryNames);
                if (error != B_OK) {
                        fUserInteractionHandler->Warn(error,
                                B_TRANSLATE("Failed to get repository names"));
                }

                int32 repositoryNameCount = repositoryNames.CountStrings();
                for (int32 i = 0; i < repositoryNameCount; i++) {
                        _AddRemoteRepository(roster, repositoryNames.StringAt(i),
                                (flags & B_REFRESH_REPOSITORIES) != 0);
                }
        }
}


void
BPackageManager::SetDebugLevel(int32 level)
{
        fDebugLevel = level;

        if (fSolver != NULL)
                fSolver->SetDebugLevel(fDebugLevel);
}


void
BPackageManager::Install(const char* const* packages, int packageCount, bool refresh)
{
        BSolverPackageSpecifierList packagesToInstall;
        _AddPackageSpecifiers(packages, packageCount, packagesToInstall);
        Install(packagesToInstall, refresh);
}


void
BPackageManager::Install(const BSolverPackageSpecifierList& packages, bool refresh)
{
        uint32 flags = B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES;
        if (refresh)
                flags |= B_REFRESH_REPOSITORIES;

        Init(flags);

        // solve
        const BSolverPackageSpecifier* unmatchedSpecifier;
        status_t error = fSolver->Install(packages, &unmatchedSpecifier);
        if (error != B_OK) {
                if (unmatchedSpecifier != NULL) {
                        DIE(error, "Failed to find a match for \"%s\"",
                                unmatchedSpecifier->SelectString().String());
                } else
                        DIE(error, "Failed to compute packages to install");
        }

        _HandleProblems();

        // install/uninstall packages
        _AnalyzeResult();
        _ConfirmChanges();
        _ApplyPackageChanges();
}


void
BPackageManager::Uninstall(const char* const* packages, int packageCount)
{
        BSolverPackageSpecifierList packagesToUninstall;
        if (!packagesToUninstall.AppendSpecifiers(packages, packageCount))
                throw std::bad_alloc();
        Uninstall(packagesToUninstall);
}


void
BPackageManager::Uninstall(const BSolverPackageSpecifierList& packages)
{
        Init(B_ADD_INSTALLED_REPOSITORIES);

        // find the packages that match the specification
        const BSolverPackageSpecifier* unmatchedSpecifier;
        PackageList foundPackages;
        status_t error = fSolver->FindPackages(packages,
                BSolver::B_FIND_INSTALLED_ONLY, foundPackages, &unmatchedSpecifier);
        if (error != B_OK) {
                if (unmatchedSpecifier != NULL) {
                        DIE(error, "Failed to find a match for \"%s\"",
                                unmatchedSpecifier->SelectString().String());
                } else
                        DIE(error, "Failed to compute packages to uninstall");
        }

        // determine the inverse base package closure for the found packages
// TODO: Optimize!
        InstalledRepository& installationRepository = InstallationRepository();
        bool foundAnotherPackage;
        do {
                foundAnotherPackage = false;
                int32 count = installationRepository.CountPackages();
                for (int32 i = 0; i < count; i++) {
                        BSolverPackage* package = installationRepository.PackageAt(i);
                        if (foundPackages.HasItem(package))
                                continue;

                        if (_FindBasePackage(foundPackages, package->Info()) >= 0) {
                                foundPackages.AddItem(package);
                                foundAnotherPackage = true;
                        }
                }
        } while (foundAnotherPackage);

        // remove the packages from the repository
        for (int32 i = 0; BSolverPackage* package = foundPackages.ItemAt(i); i++)
                installationRepository.DisablePackage(package);

        for (;;) {
                error = fSolver->VerifyInstallation(BSolver::B_VERIFY_ALLOW_UNINSTALL);
                if (error != B_OK)
                        DIE(error, "Failed to compute packages to uninstall");

                _HandleProblems();

                // (virtually) apply the result to this repository
                _AnalyzeResult();

                for (int32 i = foundPackages.CountItems() - 1; i >= 0; i--) {
                        if (!installationRepository.PackagesToDeactivate()
                                        .AddItem(foundPackages.ItemAt(i))) {
                                throw std::bad_alloc();
                        }
                }

                installationRepository.ApplyChanges();

                // verify the next specific respository
                if (!_NextSpecificInstallationLocation())
                        break;

                foundPackages.MakeEmpty();

                // NOTE: In theory, after verifying a more specific location, it would
                // be more correct to compute the inverse base package closure for the
                // packages we need to uninstall and (if anything changed) verify again.
                // In practice, however, base packages are always required with an exact
                // version (ATM). If that base package still exist in a more general
                // location (the only reason why the package requiring the base package
                // wouldn't be marked to be uninstalled as well) there shouldn't have
                // been any reason to remove it from the more specific location in the
                // first place.
        }

        _ConfirmChanges(true);
        _ApplyPackageChanges(true);
}


void
BPackageManager::Update(const char* const* packages, int packageCount)
{
        BSolverPackageSpecifierList packagesToUpdate;
        _AddPackageSpecifiers(packages, packageCount, packagesToUpdate);
        Update(packagesToUpdate);
}


void
BPackageManager::Update(const BSolverPackageSpecifierList& packages)
{
        Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
                | B_REFRESH_REPOSITORIES);

        // solve
        const BSolverPackageSpecifier* unmatchedSpecifier;
        status_t error = fSolver->Update(packages, true,
                &unmatchedSpecifier);
        if (error != B_OK) {
                if (unmatchedSpecifier != NULL) {
                        DIE(error, "Failed to find a match for \"%s\"",
                                unmatchedSpecifier->SelectString().String());
                } else
                        DIE(error, "Failed to compute packages to update");
        }

        _HandleProblems();

        // install/uninstall packages
        _AnalyzeResult();
        _ConfirmChanges();
        _ApplyPackageChanges();
}


void
BPackageManager::FullSync()
{
        Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
                | B_REFRESH_REPOSITORIES);

        // solve
        status_t error = fSolver->FullSync();
        if (error != B_OK)
                DIE(error, "Failed to compute packages to synchronize");

        _HandleProblems();

        // install/uninstall packages
        _AnalyzeResult();
        _ConfirmChanges();
        _ApplyPackageChanges();
}


void
BPackageManager::VerifyInstallation()
{
        Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
                | B_REFRESH_REPOSITORIES);

        for (;;) {
                status_t error = fSolver->VerifyInstallation();
                if (error != B_OK)
                        DIE(error, "Failed to compute package dependencies");

                _HandleProblems();

                // (virtually) apply the result to this repository
                _AnalyzeResult();
                InstallationRepository().ApplyChanges();

                // verify the next specific respository
                if (!_NextSpecificInstallationLocation())
                        break;
        }

        _ConfirmChanges();
        _ApplyPackageChanges();
}


BPackageManager::InstalledRepository&
BPackageManager::InstallationRepository()
{
        if (fInstalledRepositories.IsEmpty())
                DIE("No installation repository");

        return *fInstalledRepositories.LastItem();
}


void
BPackageManager::JobStarted(BSupportKit::BJob* job)
{
        if (dynamic_cast<FetchFileJob*>(job) != NULL) {
                FetchFileJob* fetchJob = (FetchFileJob*)job;
                fUserInteractionHandler->ProgressPackageDownloadStarted(
                        fetchJob->DownloadFileName());
        } else if (dynamic_cast<ValidateChecksumJob*>(job) != NULL) {
                fUserInteractionHandler->ProgressPackageChecksumStarted(
                        job->Title().String());
        }
}


void
BPackageManager::JobProgress(BSupportKit::BJob* job)
{
        if (dynamic_cast<FetchFileJob*>(job) != NULL) {
                FetchFileJob* fetchJob = (FetchFileJob*)job;
                fUserInteractionHandler->ProgressPackageDownloadActive(
                        fetchJob->DownloadFileName(), fetchJob->DownloadProgress(),
                        fetchJob->DownloadBytes(), fetchJob->DownloadTotalBytes());
        }
}


void
BPackageManager::JobSucceeded(BSupportKit::BJob* job)
{
        if (dynamic_cast<FetchFileJob*>(job) != NULL) {
                FetchFileJob* fetchJob = (FetchFileJob*)job;
                fUserInteractionHandler->ProgressPackageDownloadComplete(
                        fetchJob->DownloadFileName());
        } else if (dynamic_cast<ValidateChecksumJob*>(job) != NULL) {
                fUserInteractionHandler->ProgressPackageChecksumComplete(
                        job->Title().String());
        }
}


void
BPackageManager::_HandleProblems()
{
        while (fSolver->HasProblems()) {
                fUserInteractionHandler->HandleProblems();

                status_t error = fSolver->SolveAgain();
                if (error != B_OK)
                        DIE(error, "Failed to recompute packages to un/-install");
        }
}


void
BPackageManager::_AnalyzeResult()
{
        BSolverResult result;
        status_t error = fSolver->GetResult(result);
        if (error != B_OK)
                DIE(error, "Failed to compute packages to un/-install");

        InstalledRepository& installationRepository = InstallationRepository();
        PackageList& packagesToActivate
                = installationRepository.PackagesToActivate();
        PackageList& packagesToDeactivate
                = installationRepository.PackagesToDeactivate();

        PackageList potentialBasePackages;

        for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
                        i++) {
                BSolverPackage* package = element->Package();

                switch (element->Type()) {
                        case BSolverResultElement::B_TYPE_INSTALL:
                        {
                                PackageList& packageList
                                        = dynamic_cast<InstalledRepository*>(package->Repository())
                                                        != NULL
                                                ? potentialBasePackages
                                                : packagesToActivate;
                                if (!packageList.AddItem(package))
                                        throw std::bad_alloc();
                                break;
                        }

                        case BSolverResultElement::B_TYPE_UNINSTALL:
                                if (!packagesToDeactivate.AddItem(package))
                                        throw std::bad_alloc();
                                break;
                }
        }

        // Make sure base packages are installed in the same location.
        for (int32 i = 0; i < packagesToActivate.CountItems(); i++) {
                BSolverPackage* package = packagesToActivate.ItemAt(i);
                int32 index = _FindBasePackage(potentialBasePackages, package->Info());
                if (index < 0)
                        continue;

                BSolverPackage* basePackage = potentialBasePackages.RemoveItemAt(index);
                if (!packagesToActivate.AddItem(basePackage))
                        throw std::bad_alloc();
        }

        fInstallationInterface->ResultComputed(installationRepository);
}


void
BPackageManager::_ConfirmChanges(bool fromMostSpecific)
{
        // check, if there are any changes at all
        int32 count = fInstalledRepositories.CountItems();
        bool hasChanges = false;
        for (int32 i = 0; i < count; i++) {
                if (fInstalledRepositories.ItemAt(i)->HasChanges()) {
                        hasChanges = true;
                        break;
                }
        }

        if (!hasChanges)
                throw BNothingToDoException();

        fUserInteractionHandler->ConfirmChanges(fromMostSpecific);
}


void
BPackageManager::_ApplyPackageChanges(bool fromMostSpecific)
{
        int32 count = fInstalledRepositories.CountItems();
        if (fromMostSpecific) {
                for (int32 i = count - 1; i >= 0; i--)
                        _PreparePackageChanges(*fInstalledRepositories.ItemAt(i));
        } else {
                for (int32 i = 0; i < count; i++)
                        _PreparePackageChanges(*fInstalledRepositories.ItemAt(i));
        }

        for (int32 i = 0; Transaction* transaction = fTransactions.ItemAt(i); i++)
                _CommitPackageChanges(*transaction);

// TODO: Clean up the transaction directories on error!
}


void
BPackageManager::_PreparePackageChanges(
        InstalledRepository& installationRepository)
{
        if (!installationRepository.HasChanges())
                return;

        PackageList& packagesToActivate
                = installationRepository.PackagesToActivate();
        PackageList& packagesToDeactivate
                = installationRepository.PackagesToDeactivate();

        // create the transaction
        Transaction* transaction = new Transaction(installationRepository);
        if (!fTransactions.AddItem(transaction)) {
                delete transaction;
                throw std::bad_alloc();
        }

        status_t error = fInstallationInterface->PrepareTransaction(*transaction);
        if (error != B_OK)
                DIE(error, "Failed to create transaction");

        // download the new packages and prepare the transaction
        for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i);
                i++) {
                // get package URL and target entry

                BString fileName(package->Info().FileName());
                if (fileName.IsEmpty())
                        throw std::bad_alloc();

                BEntry entry;
                error = entry.SetTo(&transaction->TransactionDirectory(), fileName);
                if (error != B_OK)
                        DIE(error, "Failed to create package entry");

                RemoteRepository* remoteRepository
                        = dynamic_cast<RemoteRepository*>(package->Repository());
                if (remoteRepository != NULL) {
                        bool reusingDownload = false;

                        // Check for matching files in already existing transaction
                        // directories
                        BPath path(&transaction->TransactionDirectory());
                        BPath parent;
                        if (path.GetParent(&parent) == B_OK) {
                                BString globPath = parent.Path();
                                globPath << "/*/" << fileName;
                                glob_t globbuf;
                                if (glob(globPath.String(), GLOB_NOSORT, NULL, &globbuf) == 0) {
                                        off_t bestSize = 0;
                                        const char* bestFile = NULL;

                                        // If there are multiple matching files, pick the largest
                                        // one (the others are most likely partial downloads)
                                        for (size_t i = 0; i < globbuf.gl_pathc; i++) {
                                                off_t size = 0;
                                                BNode node(globbuf.gl_pathv[i]);
                                                if (node.GetSize(&size) == B_OK && size > bestSize) {
                                                        bestSize = size;
                                                        bestFile = globbuf.gl_pathv[i];
                                                }
                                        }

                                        // Copy the selected file into our own transaction directory
                                        path.Append(fileName);
                                        if (bestFile != NULL && BCopyEngine().CopyEntry(bestFile,
                                                path.Path()) == B_OK) {
                                                reusingDownload = true;
                                                printf("Re-using download '%s' from previous "
                                                        "transaction%s\n", bestFile,
                                                        FetchUtils::IsDownloadCompleted(
                                                                path.Path()) ? "" : " (partial)");
                                        }
                                        globfree(&globbuf);
                                }
                        }

                        // download the package (this will resume the download if the
                        // file already exists)
                        BString url = remoteRepository->Config().PackagesURL();
                        url << '/' << fileName;

                        status_t error;
retryDownload:
                        error = DownloadPackage(url, entry,
                                package->Info().Checksum());
                        if (error != B_OK) {
                                if (error == B_BAD_DATA || error == ERANGE) {
                                        // B_BAD_DATA is returned when there is a checksum
                                        // mismatch. Make sure this download is not re-used.
                                        entry.Remove();

                                        if (reusingDownload) {
                                                // Maybe the download we reused had some problem.
                                                // Try again, this time without reusing the download.
                                                printf("\nPrevious download '%s' was invalid. Redownloading.\n",
                                                        path.Path());
                                                reusingDownload = false;
                                                goto retryDownload;
                                        }
                                }
                                DIE(error, "Failed to download package %s",
                                        package->Info().Name().String());
                        }
                } else if (package->Repository() != &installationRepository) {
                        // clone the existing package
                        LocalRepository* localRepository
                                = dynamic_cast<LocalRepository*>(package->Repository());
                        if (localRepository == NULL) {
                                DIE("Internal error: repository %s is not a local repository",
                                        package->Repository()->Name().String());
                        }
                        _ClonePackageFile(localRepository, package, entry);
                }

                // add package to transaction
                if (!transaction->ActivationTransaction().AddPackageToActivate(
                                fileName)) {
                        throw std::bad_alloc();
                }
        }

        for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i);
                i++) {
                // add package to transaction
                if (!transaction->ActivationTransaction().AddPackageToDeactivate(
                                package->Info().FileName())) {
                        throw std::bad_alloc();
                }
        }
}


void
BPackageManager::_CommitPackageChanges(Transaction& transaction)
{
        InstalledRepository& installationRepository = transaction.Repository();

        fUserInteractionHandler->ProgressStartApplyingChanges(
                installationRepository);

        // commit the transaction
        BCommitTransactionResult transactionResult;
        status_t error = fInstallationInterface->CommitTransaction(transaction,
                transactionResult);
        if (error != B_OK)
                DIE(error, "Failed to commit transaction");
        if (transactionResult.Error() != B_TRANSACTION_OK)
                DIE(transactionResult);

        fUserInteractionHandler->ProgressTransactionCommitted(
                installationRepository, transactionResult);

        BEntry transactionDirectoryEntry;
        if ((error = transaction.TransactionDirectory()
                        .GetEntry(&transactionDirectoryEntry)) != B_OK
                || (error = transactionDirectoryEntry.Remove()) != B_OK) {
                fUserInteractionHandler->Warn(error,
                        B_TRANSLATE("Failed to remove transaction directory"));
        }

        fUserInteractionHandler->ProgressApplyingChangesDone(
                installationRepository);
}


void
BPackageManager::_ClonePackageFile(LocalRepository* repository,
        BSolverPackage* package, const BEntry& entry)
{
        // get source and destination path
        BPath sourcePath;
        repository->GetPackagePath(package, sourcePath);

        BPath destinationPath;
        status_t error = entry.GetPath(&destinationPath);
        if (error != B_OK) {
                DIE(error, "Failed to entry path of package file to install \"%s\"",
                        package->Info().FileName().String());
        }

        // Copy the package. Ideally we would just hard-link it, but BFS doesn't
        // support that.
        error = BCopyEngine().CopyEntry(sourcePath.Path(), destinationPath.Path());
        if (error != B_OK)
                DIE(error, "Failed to copy package file \"%s\"", sourcePath.Path());
}


int32
BPackageManager::_FindBasePackage(const PackageList& packages,
        const BPackageInfo& info)
{
        if (info.BasePackage().IsEmpty())
                return -1;

        // find the requirement matching the base package
        BPackageResolvableExpression* basePackage = NULL;
        int32 count = info.RequiresList().CountItems();
        for (int32 i = 0; i < count; i++) {
                BPackageResolvableExpression* require = info.RequiresList().ItemAt(i);
                if (require->Name() == info.BasePackage()) {
                        basePackage = require;
                        break;
                }
        }

        if (basePackage == NULL) {
                fUserInteractionHandler->Warn(B_OK, B_TRANSLATE("Package %s-%s "
                        "doesn't have a matching requires for its base package \"%s\""),
                        info.Name().String(), info.Version().ToString().String(),
                        info.BasePackage().String());
                return -1;
        }

        // find the first package matching the base package requires
        count = packages.CountItems();
        for (int32 i = 0; i < count; i++) {
                BSolverPackage* package = packages.ItemAt(i);
                if (package->Name() == basePackage->Name()
                        && package->Info().Matches(*basePackage)) {
                        return i;
                }
        }

        return -1;
}


void
BPackageManager::_AddInstalledRepository(InstalledRepository* repository)
{
        fInstallationInterface->InitInstalledRepository(*repository);

        BRepositoryBuilder(*repository)
                .AddToSolver(fSolver, repository->Location() == fLocation);
        repository->SetPriority(repository->InitialPriority());

        if (!fInstalledRepositories.AddItem(repository))
                throw std::bad_alloc();
}


void
BPackageManager::_AddRemoteRepository(BPackageRoster& roster, const char* name,
        bool refresh)
{
        BRepositoryConfig config;
        status_t error = roster.GetRepositoryConfig(name, &config);
        if (error != B_OK) {
                fUserInteractionHandler->Warn(error, B_TRANSLATE(
                        "Failed to get config for repository \"%s\". Skipping."), name);
                return;
        }

        BRepositoryCache cache;
        error = _GetRepositoryCache(roster, config, refresh, cache);
        if (error != B_OK) {
                fUserInteractionHandler->Warn(error, B_TRANSLATE(
                        "Failed to get cache for repository \"%s\". Skipping."), name);
                return;
        }

        RemoteRepository* repository = new RemoteRepository(config);
        if (!fOtherRepositories.AddItem(repository)) {
                delete repository;
                throw std::bad_alloc();
        }

        BRepositoryBuilder(*repository, cache, config.Name())
                .AddToSolver(fSolver, false);
}


status_t
BPackageManager::_GetRepositoryCache(BPackageRoster& roster,
        const BRepositoryConfig& config, bool refresh, BRepositoryCache& _cache)
{
        if (!refresh && roster.GetRepositoryCache(config.Name(), &_cache) == B_OK)
                return B_OK;

        status_t error = RefreshRepository(config);
        if (error != B_OK) {
                fUserInteractionHandler->Warn(error, B_TRANSLATE(
                        "Refreshing repository \"%s\" failed"), config.Name().String());
        }

        return roster.GetRepositoryCache(config.Name(), &_cache);
}


void
BPackageManager::_AddPackageSpecifiers(const char* const* searchStrings,
        int searchStringCount, BSolverPackageSpecifierList& specifierList)
{
        for (int i = 0; i < searchStringCount; i++) {
                const char* searchString = searchStrings[i];
                if (_IsLocalPackage(searchString)) {
                        BSolverPackage* package = _AddLocalPackage(searchString);
                        if (!specifierList.AppendSpecifier(package))
                                throw std::bad_alloc();
                } else {
                        if (!specifierList.AppendSpecifier(searchString))
                                throw std::bad_alloc();
                }
        }
}


bool
BPackageManager::_IsLocalPackage(const char* fileName)
{
        // Simple heuristic: fileName contains ".hpkg" and there's actually a file
        // it refers to.
        struct stat st;
        return strstr(fileName, ".hpkg") != NULL && stat(fileName, &st) == 0
                && S_ISREG(st.st_mode);
}


BSolverPackage*
BPackageManager::_AddLocalPackage(const char* fileName)
{
        if (fLocalRepository == NULL)
                throw std::bad_alloc();
        return fLocalRepository->AddLocalPackage(fileName);
}


bool
BPackageManager::_NextSpecificInstallationLocation()
{
        try {
                if (fLocation == B_PACKAGE_INSTALLATION_LOCATION_SYSTEM) {
                        fLocation = B_PACKAGE_INSTALLATION_LOCATION_HOME;
                        fSystemRepository->SetInstalled(false);
                        _AddInstalledRepository(fHomeRepository);
                        return true;
                }
        } catch (BFatalErrorException& e) {
                // No home repo. This is acceptable for example when we are in an haikuporter chroot.
        }

        return false;
}


status_t
BPackageManager::DownloadPackage(const BString& fileURL,
        const BEntry& targetEntry, const BString& checksum)
{
        BDecisionProvider provider;
        BContext context(provider, *this);
        return DownloadFileRequest(context, fileURL, targetEntry, checksum)
                .Process();
}


status_t
BPackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
{
        BDecisionProvider provider;
        BContext context(provider, *this);
        return BRefreshRepositoryRequest(context, repoConfig).Process();
}


// #pragma mark - RemoteRepository


BPackageManager::RemoteRepository::RemoteRepository(
        const BRepositoryConfig& config)
        :
        BSolverRepository(),
        fConfig(config)
{
}


const BRepositoryConfig&
BPackageManager::RemoteRepository::Config() const
{
        return fConfig;
}


// #pragma mark - LocalRepository


BPackageManager::LocalRepository::LocalRepository()
        :
        BSolverRepository()
{
}


BPackageManager::LocalRepository::LocalRepository(const BString& name)
        :
        BSolverRepository(name)
{
}


// #pragma mark - MiscLocalRepository


BPackageManager::MiscLocalRepository::MiscLocalRepository()
        :
        LocalRepository("local"),
        fPackagePaths()
{
        SetPriority(-127);
}


BSolverPackage*
BPackageManager::MiscLocalRepository::AddLocalPackage(const char* fileName)
{
        BSolverPackage* package;
        BRepositoryBuilder(*this).AddPackage(fileName, &package);

        fPackagePaths[package] = fileName;

        return package;
}


void
BPackageManager::MiscLocalRepository::GetPackagePath(BSolverPackage* package,
        BPath& _path)
{
        PackagePathMap::const_iterator it = fPackagePaths.find(package);
        if (it == fPackagePaths.end()) {
                DIE("Package %s not in local repository",
                        package->VersionedName().String());
        }

        status_t error = _path.SetTo(it->second.c_str());
        if (error != B_OK)
                DIE(error, "Failed to init package path %s", it->second.c_str());
}


// #pragma mark - InstalledRepository


BPackageManager::InstalledRepository::InstalledRepository(const char* name,
        BPackageInstallationLocation location, int32 priority)
        :
        LocalRepository(),
        fDisabledPackages(10),
        fPackagesToActivate(),
        fPackagesToDeactivate(),
        fInitialName(name),
        fLocation(location),
        fInitialPriority(priority)
{
}


void
BPackageManager::InstalledRepository::GetPackagePath(BSolverPackage* package,
        BPath& _path)
{
        directory_which packagesWhich;
        switch (fLocation) {
                case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM:
                        packagesWhich = B_SYSTEM_PACKAGES_DIRECTORY;
                        break;
                case B_PACKAGE_INSTALLATION_LOCATION_HOME:
                        packagesWhich = B_USER_PACKAGES_DIRECTORY;
                        break;
                default:
                        DIE("Don't know packages directory path for installation location "
                                "\"%s\"", Name().String());
        }

        BString fileName(package->Info().FileName());
        status_t error = find_directory(packagesWhich, &_path);
        if (error != B_OK || (error = _path.Append(fileName)) != B_OK) {
                DIE(error, "Failed to get path of package file \"%s\" in installation "
                        "location \"%s\"", fileName.String(), Name().String());
        }
}


void
BPackageManager::InstalledRepository::DisablePackage(BSolverPackage* package)
{
        if (fDisabledPackages.HasItem(package))
                DIE("Package %s already disabled", package->VersionedName().String());

        if (package->Repository() != this) {
                DIE("Package %s not in repository %s",
                        package->VersionedName().String(), Name().String());
        }

        // move to disabled list
        if (!fDisabledPackages.AddItem(package))
                throw std::bad_alloc();

        RemovePackage(package);
}


bool
BPackageManager::InstalledRepository::EnablePackage(BSolverPackage* package)
{
        return fDisabledPackages.RemoveItem(package);
}


bool
BPackageManager::InstalledRepository::HasChanges() const
{
        return !fPackagesToActivate.IsEmpty() || !fPackagesToDeactivate.IsEmpty();
}


void
BPackageManager::InstalledRepository::ApplyChanges()
{
        // disable packages to deactivate
        for (int32 i = 0; BSolverPackage* package = fPackagesToDeactivate.ItemAt(i);
                i++) {
                if (!fDisabledPackages.HasItem(package))
                        DisablePackage(package);
        }

        // add packages to activate
        for (int32 i = 0; BSolverPackage* package = fPackagesToActivate.ItemAt(i);
                i++) {
                status_t error = AddPackage(package->Info());
                if (error != B_OK) {
                        DIE(error, "Failed to add package %s to %s repository",
                                package->Name().String(), Name().String());
                }
        }
}


// #pragma mark - Transaction


BPackageManager::Transaction::Transaction(InstalledRepository& repository)
        :
        fRepository(repository),
        fTransaction(),
        fTransactionDirectory()
{
}


BPackageManager::Transaction::~Transaction()
{
}


// #pragma mark - InstallationInterface


BPackageManager::InstallationInterface::~InstallationInterface()
{
}


void
BPackageManager::InstallationInterface::ResultComputed(
        InstalledRepository& repository)
{
}


// #pragma mark - ClientInstallationInterface


BPackageManager::ClientInstallationInterface::ClientInstallationInterface()
        :
        fDaemonClient()
{
}


BPackageManager::ClientInstallationInterface::~ClientInstallationInterface()
{
}


void
BPackageManager::ClientInstallationInterface::InitInstalledRepository(
        InstalledRepository& repository)
{
        const char* name = repository.InitialName();
        BRepositoryBuilder(repository, name)
                .AddPackages(repository.Location(), name);
}


status_t
BPackageManager::ClientInstallationInterface::PrepareTransaction(
        Transaction& transaction)
{
        return fDaemonClient.CreateTransaction(transaction.Repository().Location(),
                transaction.ActivationTransaction(),
                transaction.TransactionDirectory());
}


status_t
BPackageManager::ClientInstallationInterface::CommitTransaction(
        Transaction& transaction, BCommitTransactionResult& _result)
{
        return fDaemonClient.CommitTransaction(transaction.ActivationTransaction(),
                _result);
}


// #pragma mark - UserInteractionHandler


BPackageManager::UserInteractionHandler::~UserInteractionHandler()
{
}


void
BPackageManager::UserInteractionHandler::HandleProblems()
{
        throw BAbortedByUserException();
}


void
BPackageManager::UserInteractionHandler::ConfirmChanges(bool fromMostSpecific)
{
        throw BAbortedByUserException();
}


void
BPackageManager::UserInteractionHandler::Warn(status_t error,
        const char* format, ...)
{
}


void
BPackageManager::UserInteractionHandler::ProgressPackageDownloadStarted(
        const char* packageName)
{
}


void
BPackageManager::UserInteractionHandler::ProgressPackageDownloadActive(
        const char* packageName, float completionPercentage, off_t bytes,
        off_t totalBytes)
{
}


void
BPackageManager::UserInteractionHandler::ProgressPackageDownloadComplete(
        const char* packageName)
{
}


void
BPackageManager::UserInteractionHandler::ProgressPackageChecksumStarted(
        const char* title)
{
}


void
BPackageManager::UserInteractionHandler::ProgressPackageChecksumComplete(
        const char* title)
{
}


void
BPackageManager::UserInteractionHandler::ProgressStartApplyingChanges(
        InstalledRepository& repository)
{
}


void
BPackageManager::UserInteractionHandler::ProgressTransactionCommitted(
        InstalledRepository& repository, const BCommitTransactionResult& result)
{
}


void
BPackageManager::UserInteractionHandler::ProgressApplyingChangesDone(
        InstalledRepository& repository)
{
}


}       // namespace BPrivate

}       // namespace BManager

}       // namespace BPackageKit