/*
* Copyright 2013-2017, 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>
*/
#include "PackageManager.h"
#include <stdio.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 "Model.h"
#include "PackageInfo.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;
typedef std::set<PackageInfoRef> PackageInfoSet;
// #pragma mark - PackageProgressListener
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)
{
}
// #pragma mark - InstallPackageAction
class InstallPackageAction : public PackageAction,
private PackageProgressListener {
public:
InstallPackageAction(PackageInfoRef package, Model* model)
:
PackageAction(PACKAGE_ACTION_INSTALL, package, model),
fLastDownloadUpdate(0)
{
}
virtual const char* Label() const
{
return B_TRANSLATE("Install");
}
virtual status_t Perform()
{
fPackageManager->Init(BPackageManager::B_ADD_INSTALLED_REPOSITORIES
| BPackageManager::B_ADD_REMOTE_REPOSITORIES
| BPackageManager::B_REFRESH_REPOSITORIES);
PackageInfoRef ref(Package());
PackageState state = ref->State();
ref->SetState(PENDING);
fPackageManager->SetCurrentActionPackage(ref, true);
fPackageManager->AddProgressListener(this);
BString packageName;
if (ref->IsLocalFile())
packageName = ref->LocalFilePath();
else
packageName = ref->Name();
const char* packageNameString = packageName.String();
try {
fPackageManager->Install(&packageNameString, 1);
} catch (BFatalErrorException ex) {
BString errorString;
errorString.SetToFormat(
"Fatal error occurred while installing package %s: "
"%s (%s)\n", packageNameString, ex.Message().String(),
ex.Details().String());
BAlert* alert(new(std::nothrow) BAlert(B_TRANSLATE("Fatal error"),
errorString, B_TRANSLATE("Close"), NULL, NULL,
B_WIDTH_AS_USUAL, B_STOP_ALERT));
if (alert != NULL)
alert->Go();
_SetDownloadedPackagesState(NONE);
ref->SetState(state);
return ex.Error();
} catch (BAbortedByUserException ex) {
fprintf(stderr, "Installation of package "
"%s aborted by user: %s\n", packageNameString,
ex.Message().String());
_SetDownloadedPackagesState(NONE);
ref->SetState(state);
return B_OK;
} catch (BNothingToDoException ex) {
fprintf(stderr, "Nothing to do while installing package "
"%s: %s\n", packageNameString, ex.Message().String());
return B_OK;
} catch (BException ex) {
fprintf(stderr, "Exception occurred while installing package "
"%s: %s\n", packageNameString, ex.Message().String());
_SetDownloadedPackagesState(NONE);
ref->SetState(state);
return B_ERROR;
}
fPackageManager->RemoveProgressListener(this);
_SetDownloadedPackagesState(ACTIVATED);
return B_OK;
}
// DownloadProgressListener
virtual void DownloadProgressChanged(const char* packageName,
float progress)
{
bigtime_t now = system_time();
if (now - fLastDownloadUpdate > 250000 || progress == 1.0) {
BString tempName(packageName);
tempName.Truncate(tempName.FindFirst('-'));
// strip version suffix off package filename
PackageInfoRef ref(FindPackageByName(tempName));
if (ref.Get() != NULL) {
ref->SetDownloadProgress(progress);
fLastDownloadUpdate = now;
}
}
}
virtual void DownloadProgressComplete(const char* packageName)
{
BString tempName(packageName);
tempName.Truncate(tempName.FindFirst('-'));
// strip version suffix off package filename
PackageInfoRef ref(FindPackageByName(tempName));
if (ref.Get() != NULL) {
ref->SetDownloadProgress(1.0);
fDownloadedPackages.insert(ref);
}
}
virtual void ConfirmedChanges(BPackageManager::InstalledRepository&
repository)
{
BPackageManager::InstalledRepository::PackageList& activationList =
repository.PackagesToActivate();
BSolverPackage* package = NULL;
for (int32 i = 0; (package = activationList.ItemAt(i)); i++) {
PackageInfoRef ref(FindPackageByName(package->Info().Name()));
if (ref.Get() != NULL)
ref->SetState(PENDING);
}
}
private:
void _SetDownloadedPackagesState(PackageState state)
{
for (PackageInfoSet::iterator it = fDownloadedPackages.begin();
it != fDownloadedPackages.end(); ++it) {
(*it)->SetState(state);
}
}
private:
bigtime_t fLastDownloadUpdate;
PackageInfoSet fDownloadedPackages;
};
// #pragma mark - UninstallPackageAction
class UninstallPackageAction : public PackageAction,
private PackageProgressListener {
public:
UninstallPackageAction(PackageInfoRef package, Model* model)
:
PackageAction(PACKAGE_ACTION_UNINSTALL, package, model)
{
}
virtual const char* Label() const
{
return B_TRANSLATE("Uninstall");
}
virtual status_t Perform()
{
fPackageManager->Init(BPackageManager::B_ADD_INSTALLED_REPOSITORIES);
PackageInfoRef ref(Package());
PackageState state = ref->State();
fPackageManager->SetCurrentActionPackage(ref, false);
fPackageManager->AddProgressListener(this);
const char* packageName = ref->Name().String();
try {
fPackageManager->Uninstall(&packageName, 1);
} catch (BFatalErrorException ex) {
BString errorString;
errorString.SetToFormat(
"Fatal error occurred while uninstalling package %s: "
"%s (%s)\n", packageName, ex.Message().String(),
ex.Details().String());
BAlert* alert(new(std::nothrow) BAlert(B_TRANSLATE("Fatal error"),
errorString, B_TRANSLATE("Close"), NULL, NULL,
B_WIDTH_AS_USUAL, B_STOP_ALERT));
if (alert != NULL)
alert->Go();
ref->SetState(state);
return ex.Error();
} catch (BAbortedByUserException ex) {
return B_OK;
} catch (BNothingToDoException ex) {
return B_OK;
} catch (BException ex) {
fprintf(stderr, "Exception occurred while uninstalling package "
"%s: %s\n", packageName, ex.Message().String());
ref->SetState(state);
return B_ERROR;
}
fPackageManager->RemoveProgressListener(this);
ref->SetState(NONE);
return B_OK;
}
void StartApplyingChanges(
BPackageManager::InstalledRepository& repository)
{
BPackageManager::InstalledRepository::PackageList& packages
= repository.PackagesToDeactivate();
for (int32 i = 0; i < packages.CountItems(); i++) {
PackageInfoRef ref(FindPackageByName(packages.ItemAt(i)
->Name()));
if (ref.Get() != NULL)
fRemovedPackages.Add(ref);
}
}
void ApplyingChangesDone(
BPackageManager::InstalledRepository& repository)
{
for (int32 i = 0; i < fRemovedPackages.CountItems(); i++)
fRemovedPackages.ItemAt(i)->SetState(NONE);
}
private:
PackageList fRemovedPackages;
};
// #pragma mark - OpenPackageAction
struct DeskbarLink {
DeskbarLink()
{
}
DeskbarLink(const BString& path, const BString& link)
:
path(path),
link(link)
{
}
DeskbarLink(const DeskbarLink& other)
:
path(other.path),
link(other.link)
{
}
DeskbarLink& operator=(const DeskbarLink& other)
{
if (this == &other)
return *this;
path = other.path;
link = other.link;
return *this;
}
bool operator==(const DeskbarLink& other)
{
return path == other.path && link == other.link;
}
bool operator!=(const DeskbarLink& other)
{
return !(*this == other);
}
BString path;
BString link;
};
typedef List<DeskbarLink, false> DeskbarLinkList;
class DeskbarLinkFinder : public BPackageContentHandler {
public:
DeskbarLinkFinder(DeskbarLinkList& foundLinks)
:
fDeskbarLinks(foundLinks)
{
}
virtual status_t HandleEntry(BPackageEntry* entry)
{
BString path = MakePath(entry);
if (path.FindFirst("data/deskbar/menu") == 0
&& entry->SymlinkPath() != NULL) {
printf("found deskbar entry: %s -> %s\n", path.String(),
entry->SymlinkPath());
fDeskbarLinks.Add(DeskbarLink(path, entry->SymlinkPath()));
}
return B_OK;
}
virtual status_t HandleEntryAttribute(BPackageEntry* entry,
BPackageEntryAttribute* attribute)
{
return B_OK;
}
virtual status_t HandleEntryDone(BPackageEntry* entry)
{
return B_OK;
}
virtual status_t HandlePackageAttribute(
const BPackageInfoAttributeValue& value)
{
return B_OK;
}
virtual void HandleErrorOccurred()
{
}
BString MakePath(const BPackageEntry* entry)
{
BString path;
while (entry != NULL) {
if (!path.IsEmpty())
path.Prepend('/', 1);
path.Prepend(entry->Name());
entry = entry->Parent();
}
return path;
}
private:
DeskbarLinkList& fDeskbarLinks;
};
class OpenPackageAction : public PackageAction {
public:
OpenPackageAction(PackageInfoRef package, Model* model,
const DeskbarLink& link)
:
PackageAction(PACKAGE_ACTION_OPEN, package, model),
fDeskbarLink(link),
fLabel(B_TRANSLATE("Open %DeskbarLink%"))
{
BString target = fDeskbarLink.link;
int32 lastPathSeparator = target.FindLast('/');
if (lastPathSeparator > 0 && lastPathSeparator + 1 < target.Length())
target.Remove(0, lastPathSeparator + 1);
fLabel.ReplaceAll("%DeskbarLink%", target);
}
virtual const char* Label() const
{
return fLabel;
}
virtual status_t Perform()
{
status_t status;
BPath path;
if (fDeskbarLink.link.FindFirst('/') == 0) {
status = path.SetTo(fDeskbarLink.link);
printf("trying to launch (absolute link): %s\n", path.Path());
} else {
int32 location = InstallLocation();
if (location == B_PACKAGE_INSTALLATION_LOCATION_SYSTEM) {
status = find_directory(B_SYSTEM_DIRECTORY, &path);
if (status != B_OK)
return status;
} else if (location == B_PACKAGE_INSTALLATION_LOCATION_HOME) {
status = find_directory(B_USER_DIRECTORY, &path);
if (status != B_OK)
return status;
} else {
return B_ERROR;
}
status = path.Append(fDeskbarLink.path);
if (status == B_OK)
status = path.GetParent(&path);
if (status == B_OK) {
status = path.Append(fDeskbarLink.link, true);
printf("trying to launch: %s\n", path.Path());
}
}
entry_ref ref;
if (status == B_OK)
status = get_ref_for_path(path.Path(), &ref);
if (status == B_OK)
status = be_roster->Launch(&ref);
return status;
}
static bool FindAppToLaunch(const PackageInfoRef& package,
DeskbarLinkList& foundLinks)
{
if (package.Get() == NULL)
return false;
int32 installLocation = InstallLocation(package);
BPath packagePath;
if (installLocation == B_PACKAGE_INSTALLATION_LOCATION_SYSTEM) {
if (find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &packagePath)
!= B_OK) {
return false;
}
} else if (installLocation == B_PACKAGE_INSTALLATION_LOCATION_HOME) {
if (find_directory(B_USER_PACKAGES_DIRECTORY, &packagePath)
!= B_OK) {
return false;
}
} else {
printf("OpenPackageAction::FindAppToLaunch(): "
"unknown install location");
return false;
}
packagePath.Append(package->FileName());
BNoErrorOutput errorOutput;
BPackageReader reader(&errorOutput);
status_t status = reader.Init(packagePath.Path());
if (status != B_OK) {
printf("OpenPackageAction::FindAppToLaunch(): "
"failed to init BPackageReader(%s): %s\n",
packagePath.Path(), strerror(status));
return false;
}
// Scan package contents for Deskbar links
DeskbarLinkFinder contentHandler(foundLinks);
status = reader.ParseContent(&contentHandler);
if (status != B_OK) {
printf("OpenPackageAction::FindAppToLaunch(): "
"failed parse package contents (%s): %s\n",
packagePath.Path(), strerror(status));
return false;
}
return foundLinks.CountItems() > 0;
}
private:
DeskbarLink fDeskbarLink;
BString fLabel;
};
// #pragma mark - PackageManager
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)
{
// TODO: Fetch information from the PackageKit
return NONE;
}
PackageActionList
PackageManager::GetPackageActions(PackageInfoRef package, Model* model)
{
PackageActionList actionList;
if (package->IsSystemPackage() || package->IsSystemDependency())
return actionList;
int32 state = package->State();
if (state == ACTIVATED || state == INSTALLED) {
actionList.Add(PackageActionRef(new UninstallPackageAction(
package, model), true));
// Add OpenPackageActions for each deskbar link found in the
// package
DeskbarLinkList foundLinks;
if (OpenPackageAction::FindAppToLaunch(package, foundLinks)
&& foundLinks.CountItems() < 4) {
for (int32 i = 0; i < foundLinks.CountItems(); i++) {
const DeskbarLink& link = foundLinks.ItemAtFast(i);
actionList.Add(PackageActionRef(new OpenPackageAction(
package, model, link), true));
}
}
} else if (state == NONE || state == UNINSTALLED) {
actionList.Add(PackageActionRef(new InstallPackageAction(package,
model), true));
}
// TODO: activation status
return actionList;
}
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) {
fprintf(stderr, "Fatal error occurred while refreshing repository: "
"%s (%s)\n", ex.Message().String(), ex.Details().String());
result = ex.Error();
} catch (BException ex) {
fprintf(stderr, "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) {
fprintf(stderr, "Fatal error occurred while downloading package: "
"%s: %s (%s)\n", fileURL.String(), ex.Message().String(),
ex.Details().String());
result = ex.Error();
} catch (BException ex) {
fprintf(stderr, "Exception occurred while downloading package "
"%s: %s\n", 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;
}
// show the window
if (windowDeleter.Detach()->Go() == 0)
throw BAbortedByUserException();
_NotifyChangesConfirmed();
}
void
PackageManager::Warn(status_t error, const char* format, ...)
{
// TODO: Show alert to user
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)
{
// TODO: implement
}
void
PackageManager::ProgressPackageChecksumComplete(const char* title)
{
// TODO: implement
}
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)
{
// TODO: implement
}
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;
if (package->State() == ACTIVATED || package->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 (package->State() == NONE
&& dynamic_cast<BPackageManager::RemoteRepository*>(
solverPackage->Repository()) == NULL) {
continue;
}
return solverPackage;
}
}
return NULL;
}
↑ V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.
↑ V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.
↑ V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.