root/src/kits/package/manager/RepositoryBuilder.cpp
/*
 * Copyright 2013-2020, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ingo Weinhold <ingo_weinhold@gmx.de>
 *              Andrew Lindesay <apl@lindesay.co.nz>
 */


#include <package/manager/RepositoryBuilder.h>

#include <errno.h>
#include <dirent.h>

#include <Entry.h>
#include <package/RepositoryCache.h>
#include <Path.h>

#include <AutoDeleter.h>
#include <AutoDeleterPosix.h>

#include "PackageManagerUtils.h"


namespace BPackageKit {

namespace BManager {

namespace BPrivate {


namespace {


class PackageInfoErrorListener : public BPackageInfo::ParseErrorListener {
public:
        PackageInfoErrorListener(const char* context)
                :
                fContext(context)
        {
        }

        virtual void OnError(const BString& message, int line, int column)
        {
                fErrors << BString().SetToFormat("%s: parse error in line %d:%d: %s\n",
                        fContext, line, column, message.String());
        }

        const BString& Errors() const
        {
                return fErrors;
        }

private:
        const char*     fContext;
        BString         fErrors;
};


} // unnamed namespace


BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository)
        :
        fRepository(repository),
        fErrorName(repository.Name()),
        fPackagePaths(NULL)
{
}


BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository,
        const BString& name, const BString& errorName)
        :
        fRepository(repository),
        fErrorName(errorName.IsEmpty() ? name : errorName),
        fPackagePaths(NULL)
{
        status_t error = fRepository.SetTo(name);
        if (error != B_OK)
                DIE(error, "failed to init %s repository", fErrorName.String());
}


BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository,
        const BRepositoryConfig& config)
        :
        fRepository(repository),
        fErrorName(fRepository.Name()),
        fPackagePaths(NULL)
{
        status_t error = fRepository.SetTo(config);
        if (error != B_OK)
                DIE(error, "failed to init %s repository", fErrorName.String());
}


BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository,
        const BRepositoryCache& cache, const BString& errorName)
        :
        fRepository(repository),
        fErrorName(errorName.IsEmpty() ? cache.Info().Name() : errorName),
        fPackagePaths(NULL)
{
        status_t error = fRepository.SetTo(cache);
        if (error != B_OK)
                DIE(error, "failed to init %s repository", fErrorName.String());
        fErrorName = fRepository.Name();
}


BRepositoryBuilder&
BRepositoryBuilder::SetPackagePathMap(BPackagePathMap* packagePaths)
{
        fPackagePaths = packagePaths;
        return *this;
}


BRepositoryBuilder&
BRepositoryBuilder::AddPackage(const BPackageInfo& info,
        const char* packageErrorName, BSolverPackage** _package)
{
        status_t error = fRepository.AddPackage(info, _package);
        if (error != B_OK) {
                DIE(error, "failed to add %s to %s repository",
                        packageErrorName != NULL
                                ? packageErrorName
                                : (BString("package ") << info.Name()).String(),
                        fErrorName.String());
        }
        return *this;
}


BRepositoryBuilder&
BRepositoryBuilder::AddPackage(const char* path, BSolverPackage** _package)
{
        // read a package info from the (HPKG or package info) file
        BPackageInfo packageInfo;

        size_t pathLength = strlen(path);
        status_t error;
        PackageInfoErrorListener errorListener(path);
        BEntry entry(path, true);

        if (!entry.Exists()) {
                DIE_DETAILS(errorListener.Errors(), B_ENTRY_NOT_FOUND,
                        "the package data file does not exist at \"%s\"", path);
        }

        struct stat entryStat;
        error = entry.GetStat(&entryStat);

        if (error != B_OK) {
                DIE_DETAILS(errorListener.Errors(), error,
                        "failed to access the package data file at \"%s\"", path);
        }

        if (entryStat.st_size == 0) {
                DIE_DETAILS(errorListener.Errors(), B_BAD_DATA,
                        "empty package data file at \"%s\"", path);
        }

        if (pathLength > 5 && strcmp(path + pathLength - 5, ".hpkg") == 0) {
                // a package file
                error = packageInfo.ReadFromPackageFile(path);
        } else {
                // a package info file (supposedly)
                error = packageInfo.ReadFromConfigFile(entry, &errorListener);
        }

        if (error != B_OK) {
                DIE_DETAILS(errorListener.Errors(), error,
                        "failed to read package data file at \"%s\"", path);
        }

        // add the package
        BSolverPackage* package;
        AddPackage(packageInfo, path, &package);

        // enter the package path in the path map, if given
        if (fPackagePaths != NULL)
                (*fPackagePaths)[package] = path;

        if (_package != NULL)
                *_package = package;

        return *this;
}


BRepositoryBuilder&
BRepositoryBuilder::AddPackages(BPackageInstallationLocation location,
        const char* locationErrorName)
{
        status_t error = fRepository.AddPackages(location);
        if (error != B_OK) {
                DIE(error, "failed to add %s packages to %s repository",
                        locationErrorName, fErrorName.String());
        }
        return *this;
}


BRepositoryBuilder&
BRepositoryBuilder::AddPackagesDirectory(const char* path)
{
        // open directory
        DirCloser dir(opendir(path));
        if (!dir.IsSet())
                DIE(errno, "failed to open package directory \"%s\"", path);

        // iterate through directory entries
        while (dirent* entry = readdir(dir.Get())) {
                // skip "." and ".."
                const char* name = entry->d_name;
                if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
                        continue;

                // stat() the entry and skip any non-file
                BPath entryPath;
                status_t error = entryPath.SetTo(path, name);
                if (error != B_OK)
                        DIE(errno, "failed to construct path");

                struct stat st;
                if (stat(entryPath.Path(), &st) != 0)
                        DIE(errno, "failed to stat() %s", entryPath.Path());

                if (!S_ISREG(st.st_mode))
                        continue;

                AddPackage(entryPath.Path());
        }

        return *this;
}


BRepositoryBuilder&
BRepositoryBuilder::AddToSolver(BSolver* solver, bool isInstalled)
{
        fRepository.SetInstalled(isInstalled);

        status_t error = solver->AddRepository(&fRepository);
        if (error != B_OK) {
                DIE(error, "failed to add %s repository to solver",
                        fErrorName.String());
        }
        return *this;
}


}       // namespace BPrivate

}       // namespace BManager

}       // namespace BPackageKit