* Copyright 2013-2024, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
* Stephan Aßmus <superstippi@gmx.de>
* Rene Gollent <rene@gollent.com>
* Julian Harnath <julian.harnath@rwth-aachen.de>
* Andrew Lindesay <apl@lindesay.co.nz>
*/
#include "PackageManager.h"
#include <Alert.h>
#include <Catalog.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <Path.h>
#include <Roster.h>
#include <package/DownloadFileRequest.h>
#include <package/manager/Exceptions.h>
#include <package/RefreshRepositoryRequest.h>
#include <package/hpkg/NoErrorOutput.h>
#include <package/hpkg/PackageContentHandler.h>
#include <package/hpkg/PackageEntry.h>
#include <package/hpkg/PackageEntryAttribute.h>
#include <package/hpkg/PackageInfoAttributeValue.h>
#include <package/hpkg/PackageReader.h>
#include <package/solver/SolverPackage.h>
#include <package/solver/SolverProblem.h>
#include <package/solver/SolverProblemSolution.h>
#include "AutoDeleter.h"
#include "AutoLocker.h"
#include "HaikuDepotConstants.h"
#include "Logger.h"
#include "OpenPackageProcess.h"
#include "PackageInfo.h"
#include "PackageUtils.h"
#include "ProblemWindow.h"
#include "ResultWindow.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PackageManager"
using namespace BPackageKit;
using namespace BPackageKit::BPrivate;
using namespace BPackageKit::BManager::BPrivate;
using BPackageKit::BRefreshRepositoryRequest;
using BPackageKit::DownloadFileRequest;
using BPackageKit::BSolver;
using BPackageKit::BSolverPackage;
using BPackageKit::BSolverRepository;
using BPackageKit::BHPKG::BNoErrorOutput;
using BPackageKit::BHPKG::BPackageContentHandler;
using BPackageKit::BHPKG::BPackageEntry;
using BPackageKit::BHPKG::BPackageEntryAttribute;
using BPackageKit::BHPKG::BPackageInfoAttributeValue;
using BPackageKit::BHPKG::BPackageReader;
PackageProgressListener::~PackageProgressListener()
{
}
void
PackageProgressListener::DownloadProgressChanged(const char* packageName,
float progress)
{
}
void
PackageProgressListener::DownloadProgressComplete(const char* packageName)
{
}
void
PackageProgressListener::ConfirmedChanges(
BPackageManager::InstalledRepository& repository)
{
}
void
PackageProgressListener::StartApplyingChanges(
BPackageManager::InstalledRepository& repository)
{
}
void
PackageProgressListener::ApplyingChangesDone(
BPackageManager::InstalledRepository& repository)
{
}
PackageManager::PackageManager(BPackageInstallationLocation location)
:
BPackageManager(location, &fClientInstallationInterface, this),
BPackageManager::UserInteractionHandler(),
fDecisionProvider(),
fClientInstallationInterface(),
fProblemWindow(NULL),
fCurrentInstallPackage(NULL),
fCurrentUninstallPackage(NULL)
{
}
PackageManager::~PackageManager()
{
if (fProblemWindow != NULL)
fProblemWindow->PostMessage(B_QUIT_REQUESTED);
}
PackageState
PackageManager::GetPackageState(const PackageInfo& package)
{
return NONE;
}
void
PackageManager::CollectPackageActions(PackageInfoRef package,
Collector<PackageActionRef>& actionList)
{
if (!package.IsSet())
return;
PackageLocalInfoRef localInfo = package->LocalInfo();
if (!localInfo.IsSet())
return;
if (localInfo->IsSystemPackage() || localInfo->IsSystemDependency())
return;
switch (PackageUtils::State(package)) {
case ACTIVATED:
case INSTALLED:
_CollectPackageActionsForActivatedOrInstalled(package, actionList);
break;
case NONE:
case UNINSTALLED:
actionList.Add(_CreateInstallPackageAction(package));
break;
case DOWNLOADING:
HDINFO("no package actions for [%s] (downloading)",
package->Name().String());
break;
case PENDING:
HDINFO("no package actions for [%s] (pending)",
package->Name().String());
break;
default:
HDFATAL("unexpected status for package [%s]",
package->Name().String());
break;
}
}
void
PackageManager::_CollectPackageActionsForActivatedOrInstalled(
PackageInfoRef package,
Collector<PackageActionRef>& actionList)
{
actionList.Add(_CreateUninstallPackageAction(package));
std::vector<DeskbarLink> foundLinks;
if (OpenPackageProcess::FindAppToLaunch(package, foundLinks) && foundLinks.size() < 4) {
std::vector<DeskbarLink>::const_iterator it;
for (it = foundLinks.begin(); it != foundLinks.end(); it++) {
const DeskbarLink& aLink = *it;
actionList.Add(_CreateOpenPackageAction(package, aLink));
}
}
}
PackageActionRef
PackageManager::_CreateUninstallPackageAction(const PackageInfoRef& package)
{
BString actionTitle = B_TRANSLATE("Uninstall %PackageTitle%");
BString packageTitle;
PackageUtils::TitleOrName(package, packageTitle);
actionTitle.ReplaceAll("%PackageTitle%", packageTitle);
BMessage message(MSG_PKG_UNINSTALL);
message.AddString(KEY_TITLE, actionTitle);
message.AddString(KEY_PACKAGE_NAME, package->Name());
return PackageActionRef(new PackageAction(actionTitle, message), true);
}
PackageActionRef
PackageManager::_CreateInstallPackageAction(const PackageInfoRef& package)
{
BString actionTitle = B_TRANSLATE("Install %PackageTitle%");
BString packageTitle;
PackageUtils::TitleOrName(package, packageTitle);
actionTitle.ReplaceAll("%PackageTitle%", packageTitle);
BMessage message(MSG_PKG_INSTALL);
message.AddString(KEY_TITLE, actionTitle);
message.AddString(KEY_PACKAGE_NAME, package->Name());
return PackageActionRef(new PackageAction(actionTitle, message), true);
}
PackageActionRef
PackageManager::_CreateOpenPackageAction(const PackageInfoRef& package, const DeskbarLink& link)
{
BString title = B_TRANSLATE("Open %DeskbarLink%");
title.ReplaceAll("%DeskbarLink%", link.Title());
BMessage deskbarLinkMessage;
if (link.Archive(&deskbarLinkMessage) != B_OK)
HDFATAL("unable to archive the deskbar link");
BMessage message(MSG_PKG_OPEN);
message.AddString(KEY_TITLE, title);
message.AddMessage(KEY_DESKBAR_LINK, &deskbarLinkMessage);
message.AddString(KEY_PACKAGE_NAME, package->Name());
return PackageActionRef(new PackageAction(title, message), true);
}
void
PackageManager::SetCurrentActionPackage(PackageInfoRef package, bool install)
{
BSolverPackage* solverPackage = _GetSolverPackage(package);
fCurrentInstallPackage = install ? solverPackage : NULL;
fCurrentUninstallPackage = install ? NULL : solverPackage;
}
status_t
PackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
{
status_t result;
try {
result = BPackageManager::RefreshRepository(repoConfig);
} catch (BFatalErrorException& ex) {
HDERROR("Fatal error occurred while refreshing repository: "
"%s (%s)", ex.Message().String(), ex.Details().String());
result = ex.Error();
} catch (BException& ex) {
HDERROR("Exception occurred while refreshing "
"repository: %s\n", ex.Message().String());
result = B_ERROR;
}
return result;
}
status_t
PackageManager::DownloadPackage(const BString& fileURL,
const BEntry& targetEntry, const BString& checksum)
{
status_t result;
try {
result = BPackageManager::DownloadPackage(fileURL, targetEntry,
checksum);
} catch (BFatalErrorException& ex) {
HDERROR("Fatal error occurred while downloading package: "
"%s: %s (%s)", fileURL.String(), ex.Message().String(),
ex.Details().String());
result = ex.Error();
} catch (BException& ex) {
HDERROR("Exception occurred while downloading package "
"%s: %s", fileURL.String(), ex.Message().String());
result = B_ERROR;
}
return result;
}
void
PackageManager::AddProgressListener(PackageProgressListener* listener)
{
fPackageProgressListeners.AddItem(listener);
}
void
PackageManager::RemoveProgressListener(PackageProgressListener* listener)
{
fPackageProgressListeners.RemoveItem(listener);
}
void
PackageManager::HandleProblems()
{
if (fProblemWindow == NULL)
fProblemWindow = new ProblemWindow;
ProblemWindow::SolverPackageSet installPackages;
ProblemWindow::SolverPackageSet uninstallPackages;
if (fCurrentInstallPackage != NULL)
installPackages.insert(fCurrentInstallPackage);
if (fCurrentUninstallPackage != NULL)
uninstallPackages.insert(fCurrentUninstallPackage);
if (!fProblemWindow->Go(fSolver,installPackages, uninstallPackages))
throw BAbortedByUserException();
}
void
PackageManager::ConfirmChanges(bool fromMostSpecific)
{
ResultWindow* window = new ResultWindow;
ObjectDeleter<ResultWindow> windowDeleter(window);
bool hasOtherChanges = false;
int32 count = fInstalledRepositories.CountItems();
if (fromMostSpecific) {
for (int32 i = count - 1; i >= 0; i--)
hasOtherChanges
|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
} else {
for (int32 i = 0; i < count; i++)
hasOtherChanges
|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
}
if (!hasOtherChanges) {
_NotifyChangesConfirmed();
return;
}
if (windowDeleter.Detach()->Go() == 0)
throw BAbortedByUserException();
_NotifyChangesConfirmed();
}
void
PackageManager::Warn(status_t error, const char* format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
if (error == B_OK)
printf("\n");
else
printf(": %s\n", strerror(error));
}
void
PackageManager::ProgressPackageDownloadStarted(const char* packageName)
{
ProgressPackageDownloadActive(packageName, 0.0f, 0, 0);
}
void
PackageManager::ProgressPackageDownloadActive(const char* packageName,
float completionPercentage, off_t bytes, off_t totalBytes)
{
for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
fPackageProgressListeners.ItemAt(i)->DownloadProgressChanged(
packageName, completionPercentage);
}
}
void
PackageManager::ProgressPackageDownloadComplete(const char* packageName)
{
for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
fPackageProgressListeners.ItemAt(i)->DownloadProgressComplete(
packageName);
}
}
void
PackageManager::ProgressPackageChecksumStarted(const char* title)
{
}
void
PackageManager::ProgressPackageChecksumComplete(const char* title)
{
}
void
PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
{
for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
fPackageProgressListeners.ItemAt(i)->StartApplyingChanges(repository);
}
void
PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
const BCommitTransactionResult& result)
{
}
void
PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
{
for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
fPackageProgressListeners.ItemAt(i)->ApplyingChangesDone(repository);
if (BPackageRoster().IsRebootNeeded()) {
BString infoString(B_TRANSLATE("A reboot is necessary to complete the "
"installation process."));
BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Reboot required"),
infoString, B_TRANSLATE("Close"), NULL, NULL,
B_WIDTH_AS_USUAL, B_INFO_ALERT);
if (alert != NULL)
alert->Go();
}
}
bool
PackageManager::_AddResults(InstalledRepository& repository,
ResultWindow* window)
{
if (!repository.HasChanges())
return false;
ProblemWindow::SolverPackageSet installPackages;
ProblemWindow::SolverPackageSet uninstallPackages;
if (fCurrentInstallPackage != NULL)
installPackages.insert(fCurrentInstallPackage);
if (fCurrentUninstallPackage != NULL)
uninstallPackages.insert(fCurrentUninstallPackage);
return window->AddLocationChanges(repository.Name(),
repository.PackagesToActivate(), installPackages,
repository.PackagesToDeactivate(), uninstallPackages);
}
void
PackageManager::_NotifyChangesConfirmed()
{
int32 count = fInstalledRepositories.CountItems();
for (int32 i = 0; i < count; i++) {
for (int32 j = 0; j < fPackageProgressListeners.CountItems(); j++) {
fPackageProgressListeners.ItemAt(j)->ConfirmedChanges(
*fInstalledRepositories.ItemAt(i));
}
}
}
BSolverPackage*
PackageManager::_GetSolverPackage(PackageInfoRef package)
{
int32 flags = BSolver::B_FIND_IN_NAME;
PackageState state = PackageUtils::State(package);
if (state == ACTIVATED || state == INSTALLED)
flags |= BSolver::B_FIND_INSTALLED_ONLY;
BObjectList<BSolverPackage> packages;
status_t result = Solver()->FindPackages(package->Name(), flags, packages);
if (result == B_OK) {
for (int32 i = 0; i < packages.CountItems(); i++) {
BSolverPackage* solverPackage = packages.ItemAt(i);
if (solverPackage->Name() != package->Name())
continue;
else if (state == NONE
&& dynamic_cast<BPackageManager::RemoteRepository*>(solverPackage->Repository())
== NULL) {
continue;
}
return solverPackage;
}
}
return NULL;
}