package daemon: Implement dependency resolution support
... for package changes performed manually by the user (i.e. adding/removing files in a packages directory). Currently only done for the system root. Alternative roots should still work as before, so this shouldn't affect haikuporter (not tested yet). Needs some more work (e.g. the GUI part).
This commit is contained in:
parent
383ac67cfa
commit
38c62dfb8b
@ -4,17 +4,19 @@ UsePrivateSystemHeaders ;
|
||||
UsePrivateHeaders app kernel shared ;
|
||||
|
||||
Server package_daemon
|
||||
:
|
||||
DebugSupport.cpp
|
||||
Job.cpp
|
||||
JobQueue.cpp
|
||||
Package.cpp
|
||||
PackageDaemon.cpp
|
||||
Root.cpp
|
||||
Volume.cpp
|
||||
:
|
||||
DebugSupport.cpp
|
||||
Job.cpp
|
||||
JobQueue.cpp
|
||||
Package.cpp
|
||||
PackageDaemon.cpp
|
||||
PackageManager.cpp
|
||||
ProblemWindow.cpp
|
||||
Root.cpp
|
||||
Volume.cpp
|
||||
:
|
||||
be package
|
||||
$(TARGET_LIBSTDC++)
|
||||
:
|
||||
package_daemon.rdef
|
||||
;
|
||||
;
|
||||
|
@ -9,6 +9,8 @@
|
||||
#define PACKAGE_H
|
||||
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <Entry.h>
|
||||
#include <Node.h>
|
||||
#include <package/PackageInfo.h>
|
||||
@ -124,6 +126,7 @@ struct PackageNodeRefHashDefinition {
|
||||
|
||||
typedef BOpenHashTable<PackageFileNameHashDefinition> PackageFileNameHashTable;
|
||||
typedef BOpenHashTable<PackageNodeRefHashDefinition> PackageNodeRefHashTable;
|
||||
typedef std::set<Package*> PackageSet;
|
||||
|
||||
|
||||
#endif // PACKAGE_H
|
||||
|
450
src/servers/package/PackageManager.cpp
Normal file
450
src/servers/package/PackageManager.cpp
Normal file
@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "PackageManager.h"
|
||||
|
||||
#include <Alert.h>
|
||||
#include <Notification.h>
|
||||
#include <package/DownloadFileRequest.h>
|
||||
#include <package/RefreshRepositoryRequest.h>
|
||||
#include <package/solver/SolverPackage.h>
|
||||
#include <package/solver/SolverPackageSpecifierList.h>
|
||||
#include <package/solver/SolverProblem.h>
|
||||
#include <package/solver/SolverProblemSolution.h>
|
||||
|
||||
#include <package/manager/Exceptions.h>
|
||||
#include <package/manager/RepositoryBuilder.h>
|
||||
#include <Server.h>
|
||||
|
||||
#include "ProblemWindow.h"
|
||||
#include "Root.h"
|
||||
#include "Volume.h"
|
||||
|
||||
|
||||
using BPackageKit::BManager::BPrivate::BAbortedByUserException;
|
||||
using BPackageKit::BManager::BPrivate::BFatalErrorException;
|
||||
using BPackageKit::BManager::BPrivate::BRepositoryBuilder;
|
||||
|
||||
|
||||
PackageManager::PackageManager(Root* root, Volume* volume)
|
||||
:
|
||||
BPackageManager(volume->Location()),
|
||||
BPackageManager::UserInteractionHandler(),
|
||||
fRoot(root),
|
||||
fVolume(volume),
|
||||
fContext(*this, *this),
|
||||
fSolverPackages(),
|
||||
fPackagesAddedByUser(),
|
||||
fPackagesRemovedByUser(),
|
||||
fProblemWindow(NULL)
|
||||
{
|
||||
fInstallationInterface = this;
|
||||
fRequestHandler = this;
|
||||
fUserInteractionHandler = this;
|
||||
}
|
||||
|
||||
|
||||
PackageManager::~PackageManager()
|
||||
{
|
||||
if (fProblemWindow != NULL)
|
||||
fProblemWindow->PostMessage(B_QUIT_REQUESTED);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::HandleUserChanges()
|
||||
{
|
||||
const PackageSet& packagesToActivate = fVolume->PackagesToBeActivated();
|
||||
const PackageSet& packagesToDeactivate = fVolume->PackagesToBeDeactivated();
|
||||
|
||||
if (packagesToActivate.empty() && packagesToDeactivate.empty())
|
||||
return;
|
||||
|
||||
if (packagesToActivate.empty()) {
|
||||
// only packages removed -- use uninstall mode
|
||||
Init(B_ADD_INSTALLED_REPOSITORIES);
|
||||
|
||||
BSolverPackageSpecifierList packagesToUninstall;
|
||||
for (PackageSet::const_iterator it = packagesToDeactivate.begin();
|
||||
it != packagesToDeactivate.end(); ++it) {
|
||||
BSolverPackage* solverPackage = _SolverPackageFor(*it);
|
||||
if (solverPackage == NULL)
|
||||
continue;
|
||||
|
||||
if (!packagesToUninstall.AppendSpecifier(solverPackage))
|
||||
throw std::bad_alloc();
|
||||
fPackagesRemovedByUser.insert(solverPackage);
|
||||
}
|
||||
|
||||
if (fPackagesRemovedByUser.empty())
|
||||
return;
|
||||
|
||||
Uninstall(packagesToUninstall);
|
||||
} else {
|
||||
// packages added and (possibly) remove -- manually add/remove those
|
||||
// from the repository and use verify mode
|
||||
Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
|
||||
| B_REFRESH_REPOSITORIES);
|
||||
|
||||
// disable and remove uninstalled packages
|
||||
InstalledRepository& repository = InstallationRepository();
|
||||
for (PackageSet::const_iterator it = packagesToDeactivate.begin();
|
||||
it != packagesToDeactivate.end(); ++it) {
|
||||
BSolverPackage* solverPackage = _SolverPackageFor(*it);
|
||||
if (solverPackage == NULL)
|
||||
continue;
|
||||
|
||||
repository.DisablePackage(solverPackage);
|
||||
if (!repository.PackagesToDeactivate().AddItem(solverPackage))
|
||||
throw std::bad_alloc();
|
||||
fPackagesRemovedByUser.insert(solverPackage);
|
||||
}
|
||||
|
||||
// add new packages
|
||||
BRepositoryBuilder repositoryBuilder(repository);
|
||||
for (PackageSet::const_iterator it = packagesToActivate.begin();
|
||||
it != packagesToActivate.end(); ++it) {
|
||||
Package* package = *it;
|
||||
BSolverPackage* solverPackage;
|
||||
repositoryBuilder.AddPackage(package->Info(), NULL, &solverPackage);
|
||||
fSolverPackages[package] = solverPackage;
|
||||
if (!repository.PackagesToActivate().AddItem(solverPackage))
|
||||
throw std::bad_alloc();
|
||||
fPackagesAddedByUser.insert(solverPackage);
|
||||
}
|
||||
|
||||
if (fPackagesRemovedByUser.empty() && fPackagesAddedByUser.empty())
|
||||
return;
|
||||
|
||||
// TODO: When packages are moved out of the packages directory, we can't create
|
||||
// a complete "old-state" directory.
|
||||
|
||||
VerifyInstallation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::InitInstalledRepository(InstalledRepository& repository)
|
||||
{
|
||||
const char* name = repository.InitialName();
|
||||
BRepositoryBuilder repositoryBuilder(repository, name);
|
||||
|
||||
if (Volume* volume = fRoot->GetVolume(repository.Location())) {
|
||||
for (PackageFileNameHashTable::Iterator it
|
||||
= volume->PackagesByFileName().GetIterator(); it.HasNext();) {
|
||||
Package* package = it.Next();
|
||||
if (package->IsActive()) {
|
||||
BSolverPackage* solverPackage;
|
||||
repositoryBuilder.AddPackage(package->Info(), NULL,
|
||||
&solverPackage);
|
||||
fSolverPackages[package] = solverPackage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::ResultComputed(InstalledRepository& repository)
|
||||
{
|
||||
// Normalize the result, i.e. remove the packages added by the user which
|
||||
// have been removed again in the problem resolution process, and vice
|
||||
// versa.
|
||||
if (repository.Location() != fVolume->Location())
|
||||
return;
|
||||
|
||||
PackageList& packagesToActivate = repository.PackagesToActivate();
|
||||
PackageList& packagesToDeactivate = repository.PackagesToDeactivate();
|
||||
|
||||
for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i);
|
||||
i++) {
|
||||
if (fPackagesAddedByUser.erase(package) == 0)
|
||||
continue;
|
||||
|
||||
for (SolverPackageMap::iterator it = fSolverPackages.begin();
|
||||
it != fSolverPackages.end(); ++it) {
|
||||
if (it->second == package) {
|
||||
fSolverPackages.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
repository.EnablePackage(package);
|
||||
packagesToDeactivate.RemoveItemAt(i--);
|
||||
packagesToActivate.RemoveItem(package);
|
||||
repository.DeletePackage(package);
|
||||
}
|
||||
|
||||
for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i);
|
||||
i++) {
|
||||
if (fPackagesRemovedByUser.erase(package) == 0)
|
||||
continue;
|
||||
|
||||
repository.EnablePackage(package);
|
||||
packagesToActivate.RemoveItemAt(i--);
|
||||
packagesToDeactivate.RemoveItem(package);
|
||||
|
||||
// Note: We keep the package activated, but nonetheless it is gone,
|
||||
// since the user has removed it from the packages directory. So unless
|
||||
// the user moves it back, we won't find it upon next reboot.
|
||||
// TODO: We probable even run into trouble when the user moves in a
|
||||
// replacement afterward.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
PackageManager::PrepareTransaction(Transaction& transaction)
|
||||
{
|
||||
Volume* volume = fRoot->GetVolume(transaction.Repository().Location());
|
||||
if (volume == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
return volume->CreateTransaction(transaction.Repository().Location(),
|
||||
transaction.ActivationTransaction(),
|
||||
transaction.TransactionDirectory());
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
PackageManager::CommitTransaction(Transaction& transaction,
|
||||
BDaemonClient::BCommitTransactionResult& _result)
|
||||
{
|
||||
debug_printf("PackageManager::CommitTransaction()\n");
|
||||
Volume* volume = fRoot->GetVolume(transaction.Repository().Location());
|
||||
if (volume == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// Get the packages that have already been added to/removed from the
|
||||
// packages directory and thus need to be handled specially by
|
||||
// Volume::CommitTransaction().
|
||||
PackageSet packagesAlreadyAdded;
|
||||
PackageSet packagesAlreadyRemoved;
|
||||
if (volume == fVolume) {
|
||||
const PackageSet& packagesToActivate = volume->PackagesToBeActivated();
|
||||
for (PackageSet::const_iterator it = packagesToActivate.begin();
|
||||
it != packagesToActivate.end(); ++it) {
|
||||
Package* package = *it;
|
||||
if (fPackagesAddedByUser.find(_SolverPackageFor(package))
|
||||
!= fPackagesAddedByUser.end()) {
|
||||
packagesAlreadyAdded.insert(package);
|
||||
}
|
||||
}
|
||||
|
||||
const PackageSet& packagesToDeactivate
|
||||
= volume->PackagesToBeDeactivated();
|
||||
for (PackageSet::const_iterator it = packagesToDeactivate.begin();
|
||||
it != packagesToDeactivate.end(); ++it) {
|
||||
Package* package = *it;
|
||||
if (fPackagesRemovedByUser.find(_SolverPackageFor(package))
|
||||
!= fPackagesRemovedByUser.end()) {
|
||||
packagesAlreadyRemoved.insert(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
debug_printf(" activating %ld (%zu) packages\n", transaction.ActivationTransaction().PackagesToActivate().CountStrings(), packagesAlreadyAdded.size());
|
||||
debug_printf(" deactivating %ld (%zu) packages\n", transaction.ActivationTransaction().PackagesToDeactivate().CountStrings(), packagesAlreadyRemoved.size());
|
||||
|
||||
volume->CommitTransaction(transaction.ActivationTransaction(),
|
||||
packagesAlreadyAdded, packagesAlreadyRemoved, _result);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
PackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
|
||||
{
|
||||
return BRefreshRepositoryRequest(fContext, repoConfig).Process();
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
PackageManager::DownloadPackage(const BString& fileURL,
|
||||
const BEntry& targetEntry, const BString& checksum)
|
||||
{
|
||||
return DownloadFileRequest(fContext, fileURL, targetEntry, checksum)
|
||||
.Process();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::HandleProblems()
|
||||
{
|
||||
_InitGui();
|
||||
|
||||
if (fProblemWindow == NULL)
|
||||
fProblemWindow = new ProblemWindow;
|
||||
|
||||
if (!fProblemWindow->Go(fSolver, fPackagesAddedByUser,
|
||||
fPackagesRemovedByUser)) {
|
||||
throw BAbortedByUserException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::ConfirmChanges(bool fromMostSpecific)
|
||||
{
|
||||
// Check whether there are any changes other than those made by the user.
|
||||
BString alertText(
|
||||
"The following additional package changes have to be made:");
|
||||
bool hasOtherChanges = false;
|
||||
int32 count = fInstalledRepositories.CountItems();
|
||||
if (fromMostSpecific) {
|
||||
for (int32 i = count - 1; i >= 0; i--)
|
||||
hasOtherChanges
|
||||
|= _GetResultText(*fInstalledRepositories.ItemAt(i), alertText);
|
||||
} else {
|
||||
for (int32 i = 0; i < count; i++)
|
||||
hasOtherChanges
|
||||
|= _GetResultText(*fInstalledRepositories.ItemAt(i), alertText);
|
||||
}
|
||||
|
||||
if (!hasOtherChanges)
|
||||
return;
|
||||
|
||||
// show an alert
|
||||
_InitGui();
|
||||
BAlert* alert = new BAlert("Package changes", alertText,
|
||||
"Cancel", "Apply changes");
|
||||
alert->SetShortcut(0, B_ESCAPE);
|
||||
if (alert->Go() == 0)
|
||||
throw BAbortedByUserException();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::Warn(status_t error, const char* format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
BString message;
|
||||
message.SetToFormatVarArgs(format, args);
|
||||
va_end(args);
|
||||
|
||||
if (error != B_OK)
|
||||
message << BString().SetToFormat(": %s", strerror(error));
|
||||
|
||||
BNotification notification(B_ERROR_NOTIFICATION);
|
||||
notification.SetTitle("Package Daemon");
|
||||
notification.SetContent(message);
|
||||
notification.Send();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
|
||||
const char* transactionDirectoryName)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
PackageManager::YesNoDecisionNeeded(const BString& description,
|
||||
const BString& question, const BString& yes, const BString& no,
|
||||
const BString& defaultChoice)
|
||||
{
|
||||
// ATM we need the BDecisionProvider object for the BContext, but we don't
|
||||
// execute any request which actually uses it.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::JobFailed(BJob* job)
|
||||
{
|
||||
// TODO:...
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::JobAborted(BJob* job)
|
||||
{
|
||||
// TODO:...
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
PackageManager::_GetResultText(InstalledRepository& repository,
|
||||
BString& _text)
|
||||
{
|
||||
if (!repository.HasChanges())
|
||||
return false;
|
||||
|
||||
bool hasOtherChanges = false;
|
||||
bool isTargetLocation
|
||||
= repository.Location() == fVolume->Location();
|
||||
|
||||
BString text = BString().SetToFormat("\n in %s:",
|
||||
repository.Name().String());
|
||||
|
||||
PackageList& packagesToActivate = repository.PackagesToActivate();
|
||||
PackageList& packagesToDeactivate = repository.PackagesToDeactivate();
|
||||
|
||||
for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i);
|
||||
i++) {
|
||||
if (isTargetLocation
|
||||
&& fPackagesAddedByUser.find(package)
|
||||
!= fPackagesAddedByUser.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
text << BString().SetToFormat(
|
||||
"\n install package %s from repository %s\n",
|
||||
package->Info().FileName().String(),
|
||||
package->Repository()->Name().String());
|
||||
hasOtherChanges = true;
|
||||
}
|
||||
|
||||
for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i);
|
||||
i++) {
|
||||
if (isTargetLocation
|
||||
&& fPackagesRemovedByUser.find(package)
|
||||
!= fPackagesRemovedByUser.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
text << BString().SetToFormat(
|
||||
"\n uninstall package %s\n", package->VersionedName().String());
|
||||
hasOtherChanges = true;
|
||||
}
|
||||
|
||||
if (!hasOtherChanges)
|
||||
return false;
|
||||
|
||||
_text << text;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
BSolverPackage*
|
||||
PackageManager::_SolverPackageFor(Package* package) const
|
||||
{
|
||||
SolverPackageMap::const_iterator it = fSolverPackages.find(package);
|
||||
return it != fSolverPackages.end() ? it->second : NULL;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PackageManager::_InitGui()
|
||||
{
|
||||
BServer* server = dynamic_cast<BServer*>(be_app);
|
||||
if (server == NULL || server->InitGUIContext() != B_OK)
|
||||
throw BFatalErrorException("failed to initialize the GUI");
|
||||
}
|
116
src/servers/package/PackageManager.h
Normal file
116
src/servers/package/PackageManager.h
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef PACKAGE_MANAGER_H
|
||||
#define PACKAGE_MANAGER_H
|
||||
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <package/Context.h>
|
||||
#include <package/Job.h>
|
||||
|
||||
#include <package/DaemonClient.h>
|
||||
#include <package/manager/PackageManager.h>
|
||||
|
||||
|
||||
using BPackageKit::BContext;
|
||||
using BPackageKit::BDecisionProvider;
|
||||
using BPackageKit::BJob;
|
||||
using BPackageKit::BJobStateListener;
|
||||
using BPackageKit::BPackageInstallationLocation;
|
||||
using BPackageKit::BRepositoryConfig;
|
||||
using BPackageKit::BSolverPackage;
|
||||
|
||||
using BPackageKit::BPrivate::BDaemonClient;
|
||||
using BPackageKit::BManager::BPrivate::BPackageManager;
|
||||
|
||||
|
||||
class Package;
|
||||
class ProblemWindow;
|
||||
class Root;
|
||||
class Volume;
|
||||
|
||||
|
||||
class PackageManager : public BPackageManager,
|
||||
private BPackageManager::InstallationInterface,
|
||||
private BPackageManager::RequestHandler,
|
||||
private BPackageManager::UserInteractionHandler,
|
||||
private BDecisionProvider, private BJobStateListener {
|
||||
public:
|
||||
PackageManager(Root* root, Volume* volume);
|
||||
~PackageManager();
|
||||
|
||||
void HandleUserChanges();
|
||||
|
||||
private:
|
||||
// InstallationInterface
|
||||
virtual void InitInstalledRepository(
|
||||
InstalledRepository& repository);
|
||||
virtual void ResultComputed(InstalledRepository& repository);
|
||||
|
||||
virtual status_t PrepareTransaction(Transaction& transaction);
|
||||
virtual status_t CommitTransaction(Transaction& transaction,
|
||||
BDaemonClient::BCommitTransactionResult&
|
||||
_result);
|
||||
|
||||
private:
|
||||
// RequestHandler
|
||||
virtual status_t RefreshRepository(
|
||||
const BRepositoryConfig& repoConfig);
|
||||
virtual status_t DownloadPackage(const BString& fileURL,
|
||||
const BEntry& targetEntry,
|
||||
const BString& checksum);
|
||||
|
||||
private:
|
||||
// UserInteractionHandler
|
||||
virtual void HandleProblems();
|
||||
virtual void ConfirmChanges(bool fromMostSpecific);
|
||||
|
||||
virtual void Warn(status_t error, const char* format, ...);
|
||||
virtual void ProgressStartApplyingChanges(
|
||||
InstalledRepository& repository);
|
||||
virtual void ProgressTransactionCommitted(
|
||||
InstalledRepository& repository,
|
||||
const char* transactionDirectoryName);
|
||||
virtual void ProgressApplyingChangesDone(
|
||||
InstalledRepository& repository);
|
||||
|
||||
private:
|
||||
// BDecisionProvider
|
||||
virtual bool YesNoDecisionNeeded(const BString& description,
|
||||
const BString& question,
|
||||
const BString& yes, const BString& no,
|
||||
const BString& defaultChoice);
|
||||
|
||||
private:
|
||||
// BJobStateListener
|
||||
virtual void JobFailed(BJob* job);
|
||||
virtual void JobAborted(BJob* job);
|
||||
|
||||
private:
|
||||
typedef std::set<BSolverPackage*> SolverPackageSet;
|
||||
typedef std::map<Package*, BSolverPackage*> SolverPackageMap;
|
||||
|
||||
private:
|
||||
bool _GetResultText(InstalledRepository& repository,
|
||||
BString& _text);
|
||||
|
||||
BSolverPackage* _SolverPackageFor(Package* package) const;
|
||||
|
||||
void _InitGui();
|
||||
|
||||
private:
|
||||
Root* fRoot;
|
||||
Volume* fVolume;
|
||||
BContext fContext;
|
||||
SolverPackageMap fSolverPackages;
|
||||
SolverPackageSet fPackagesAddedByUser;
|
||||
SolverPackageSet fPackagesRemovedByUser;
|
||||
ProblemWindow* fProblemWindow;
|
||||
};
|
||||
|
||||
|
||||
#endif // PACKAGE_MANAGER_H
|
287
src/servers/package/ProblemWindow.cpp
Normal file
287
src/servers/package/ProblemWindow.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "ProblemWindow.h"
|
||||
|
||||
#include <Button.h>
|
||||
#include <GroupView.h>
|
||||
#include <LayoutBuilder.h>
|
||||
#include <RadioButton.h>
|
||||
#include <StringView.h>
|
||||
#include <package/solver/Solver.h>
|
||||
#include <package/solver/SolverPackage.h>
|
||||
#include <package/solver/SolverProblem.h>
|
||||
#include <package/solver/SolverProblemSolution.h>
|
||||
|
||||
#include <AutoLocker.h>
|
||||
#include <package/manager/Exceptions.h>
|
||||
|
||||
|
||||
using namespace BPackageKit;
|
||||
|
||||
using BPackageKit::BManager::BPrivate::BFatalErrorException;
|
||||
|
||||
|
||||
static const int32 kRetryMessage = 'rtry';
|
||||
static const int32 kUpdateRetryButtonMessage = 'uprt';
|
||||
|
||||
|
||||
struct ProblemWindow::Solution {
|
||||
BSolverProblem* fProblem;
|
||||
const BSolverProblemSolution* fSolution;
|
||||
|
||||
Solution()
|
||||
:
|
||||
fProblem(NULL),
|
||||
fSolution(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
Solution(BSolverProblem* problem, const BSolverProblemSolution* solution)
|
||||
:
|
||||
fProblem(problem),
|
||||
fSolution(solution)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ProblemWindow::ProblemWindow()
|
||||
:
|
||||
BWindow(BRect(0, 0, 400, 300), "Package problems", B_TITLED_WINDOW_LOOK,
|
||||
B_NORMAL_WINDOW_FEEL,
|
||||
B_ASYNCHRONOUS_CONTROLS | B_NOT_MINIMIZABLE | B_AUTO_UPDATE_SIZE_LIMITS,
|
||||
B_ALL_WORKSPACES),
|
||||
fDoneSemaphore(-1),
|
||||
fClientWaiting(false),
|
||||
fAccepted(false),
|
||||
fContainerView(NULL),
|
||||
fCancelButton(NULL),
|
||||
fRetryButton(NULL),
|
||||
fSolutions(),
|
||||
fPackagesAddedByUser(NULL),
|
||||
fPackagesRemovedByUser(NULL)
|
||||
|
||||
{
|
||||
fDoneSemaphore = create_sem(0, "package problems");
|
||||
if (fDoneSemaphore < 0)
|
||||
throw std::bad_alloc();
|
||||
|
||||
BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING)
|
||||
.SetInsets(B_USE_SMALL_INSETS)
|
||||
.Add(new BStringView(NULL,
|
||||
"The following problems have been encountered. Please select a "
|
||||
"solution for each:"))
|
||||
// TODO: Use a scroll view!
|
||||
.Add(fContainerView = new BGroupView(B_VERTICAL, 0))
|
||||
.AddGroup(B_HORIZONTAL)
|
||||
.Add(fCancelButton = new BButton("Cancel", new BMessage(B_CANCEL)))
|
||||
.AddGlue()
|
||||
.Add(fRetryButton = new BButton("Retry",
|
||||
new BMessage(kRetryMessage)))
|
||||
.End();
|
||||
}
|
||||
|
||||
|
||||
ProblemWindow::~ProblemWindow()
|
||||
{
|
||||
if (fDoneSemaphore >= 0)
|
||||
delete_sem(fDoneSemaphore);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ProblemWindow::Go(BSolver* solver, const SolverPackageSet& packagesAddedByUser,
|
||||
const SolverPackageSet& packagesRemovedByUser)
|
||||
{
|
||||
AutoLocker<ProblemWindow> locker(this);
|
||||
|
||||
fPackagesAddedByUser = &packagesAddedByUser;
|
||||
fPackagesRemovedByUser = &packagesRemovedByUser;
|
||||
|
||||
_ClearProblemsGui();
|
||||
_AddProblemsGui(solver);
|
||||
|
||||
fCancelButton->SetEnabled(true);
|
||||
fRetryButton->SetEnabled(false);
|
||||
|
||||
if (IsHidden()) {
|
||||
CenterOnScreen();
|
||||
Show();
|
||||
}
|
||||
|
||||
fAccepted = false;
|
||||
fClientWaiting = true;
|
||||
|
||||
locker.Unlock();
|
||||
|
||||
while (acquire_sem(fDoneSemaphore) == B_INTERRUPTED) {
|
||||
}
|
||||
|
||||
locker.Lock();
|
||||
if (!locker.IsLocked() || !fAccepted || !_AnySolutionSelected())
|
||||
return false;
|
||||
|
||||
// set the solutions
|
||||
for (SolutionMap::const_iterator it = fSolutions.begin();
|
||||
it != fSolutions.end(); ++it) {
|
||||
BRadioButton* button = it->first;
|
||||
if (button->Value() == B_CONTROL_ON) {
|
||||
const Solution& solution = it->second;
|
||||
status_t error = solver->SelectProblemSolution(solution.fProblem,
|
||||
solution.fSolution);
|
||||
if (error != B_OK)
|
||||
throw BFatalErrorException(error, "failed to set solution");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ProblemWindow::QuitRequested()
|
||||
{
|
||||
if (fClientWaiting) {
|
||||
fClientWaiting = false;
|
||||
release_sem(fDoneSemaphore);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ProblemWindow::MessageReceived(BMessage* message)
|
||||
{
|
||||
switch (message->what) {
|
||||
case B_CANCEL:
|
||||
Hide();
|
||||
fClientWaiting = false;
|
||||
release_sem(fDoneSemaphore);
|
||||
break;
|
||||
case kRetryMessage:
|
||||
fCancelButton->SetEnabled(false);
|
||||
fRetryButton->SetEnabled(false);
|
||||
fAccepted = true;
|
||||
fClientWaiting = false;
|
||||
release_sem(fDoneSemaphore);
|
||||
break;
|
||||
case kUpdateRetryButtonMessage:
|
||||
fRetryButton->SetEnabled(_AnySolutionSelected());
|
||||
break;
|
||||
default:
|
||||
BWindow::MessageReceived(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ProblemWindow::_ClearProblemsGui()
|
||||
{
|
||||
fSolutions.clear();
|
||||
|
||||
int32 count = fContainerView->CountChildren();
|
||||
for (int32 i = count - 1; i >= 0; i--) {
|
||||
BView* child = fContainerView->ChildAt(i);
|
||||
fContainerView->RemoveChild(child);
|
||||
delete child;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ProblemWindow::_AddProblemsGui(BSolver* solver)
|
||||
{
|
||||
rgb_color evenBackground = ui_color(B_LIST_BACKGROUND_COLOR);
|
||||
rgb_color oddBackground = tint_color(evenBackground, 1.04);
|
||||
|
||||
int32 problemCount = solver->CountProblems();
|
||||
for (int32 i = 0; i < problemCount; i++) {
|
||||
_AddProblem(solver->ProblemAt(i),
|
||||
(i & 1) == 0 ? evenBackground : oddBackground);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ProblemWindow::_AddProblem(BSolverProblem* problem,
|
||||
const rgb_color& backgroundColor)
|
||||
{
|
||||
BGroupView* problemGroup = new BGroupView(B_VERTICAL);
|
||||
fContainerView->AddChild(problemGroup);
|
||||
problemGroup->GroupLayout()->SetInsets(B_USE_SMALL_INSETS);
|
||||
problemGroup->SetViewColor(backgroundColor);
|
||||
|
||||
BStringView* problemView = new BStringView(NULL, problem->ToString());
|
||||
problemGroup->AddChild(problemView);
|
||||
BFont problemFont;
|
||||
problemView->GetFont(&problemFont);
|
||||
problemFont.SetFace(B_BOLD_FACE);
|
||||
problemView->SetFont(&problemFont);
|
||||
|
||||
int32 solutionCount = problem->CountSolutions();
|
||||
for (int32 k = 0; k < solutionCount; k++) {
|
||||
const BSolverProblemSolution* solution = problem->SolutionAt(k);
|
||||
BRadioButton* solutionButton = new BRadioButton(
|
||||
BString().SetToFormat("solution %" B_PRId32 ":", k + 1),
|
||||
new BMessage(kUpdateRetryButtonMessage));
|
||||
problemGroup->AddChild(solutionButton);
|
||||
|
||||
BGroupLayout* elementsGroup = new BGroupLayout(B_VERTICAL);
|
||||
problemGroup->AddChild(elementsGroup);
|
||||
elementsGroup->SetInsets(20, 0, 0, 0);
|
||||
|
||||
int32 elementCount = solution->CountElements();
|
||||
for (int32 l = 0; l < elementCount; l++) {
|
||||
const BSolverProblemSolutionElement* element
|
||||
= solution->ElementAt(l);
|
||||
BStringView* elementView = new BStringView(NULL,
|
||||
BString().SetToFormat("- %s",
|
||||
_SolutionElementText(element).String()));
|
||||
elementsGroup->AddView(elementView);
|
||||
}
|
||||
|
||||
fSolutions[solutionButton] = Solution(problem, solution);
|
||||
}
|
||||
|
||||
BRadioButton* ignoreButton = new BRadioButton("ignore problem for now",
|
||||
new BMessage(kUpdateRetryButtonMessage));
|
||||
problemGroup->AddChild(ignoreButton);
|
||||
ignoreButton->SetValue(B_CONTROL_ON);
|
||||
}
|
||||
|
||||
|
||||
BString
|
||||
ProblemWindow::_SolutionElementText(
|
||||
const BSolverProblemSolutionElement* element) const
|
||||
{
|
||||
// Reword text for B_ALLOW_DEINSTALLATION, if the package has been added
|
||||
// by the user.
|
||||
BSolverPackage* package = element->SourcePackage();
|
||||
if (element->Type() == BSolverProblemSolutionElement::B_ALLOW_DEINSTALLATION
|
||||
&& package != NULL
|
||||
&& fPackagesAddedByUser->find(package) != fPackagesAddedByUser->end()) {
|
||||
return BString("don't activate package %source%").ReplaceAll(
|
||||
"%source%", package->VersionedName());
|
||||
}
|
||||
|
||||
return element->ToString();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ProblemWindow::_AnySolutionSelected() const
|
||||
{
|
||||
for (SolutionMap::const_iterator it = fSolutions.begin();
|
||||
it != fSolutions.end(); ++it) {
|
||||
BRadioButton* button = it->first;
|
||||
if (button->Value() == B_CONTROL_ON)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
78
src/servers/package/ProblemWindow.h
Normal file
78
src/servers/package/ProblemWindow.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef PROBLEM_WINDOW_H
|
||||
#define PROBLEM_WINDOW_H
|
||||
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <Window.h>
|
||||
|
||||
|
||||
namespace BPackageKit {
|
||||
class BSolver;
|
||||
class BSolverPackage;
|
||||
class BSolverProblem;
|
||||
class BSolverProblemSolution;
|
||||
class BSolverProblemSolutionElement;
|
||||
}
|
||||
|
||||
using BPackageKit::BSolver;
|
||||
using BPackageKit::BSolverPackage;
|
||||
using BPackageKit::BSolverProblem;
|
||||
using BPackageKit::BSolverProblemSolution;
|
||||
using BPackageKit::BSolverProblemSolutionElement;
|
||||
|
||||
class BButton;
|
||||
class BGroupView;
|
||||
class BRadioButton;
|
||||
|
||||
|
||||
class ProblemWindow : public BWindow {
|
||||
public:
|
||||
typedef std::set<BSolverPackage*> SolverPackageSet;
|
||||
|
||||
public:
|
||||
ProblemWindow();
|
||||
virtual ~ProblemWindow();
|
||||
|
||||
bool Go(BSolver* solver,
|
||||
const SolverPackageSet& packagesAddedByUser,
|
||||
const SolverPackageSet&
|
||||
packagesRemovedByUser);
|
||||
|
||||
virtual bool QuitRequested();
|
||||
virtual void MessageReceived(BMessage* message);
|
||||
|
||||
private:
|
||||
struct Solution;
|
||||
|
||||
typedef std::map<BRadioButton*, Solution> SolutionMap;
|
||||
|
||||
private:
|
||||
void _ClearProblemsGui();
|
||||
void _AddProblemsGui(BSolver* solver);
|
||||
void _AddProblem(BSolverProblem* problem,
|
||||
const rgb_color& backgroundColor);
|
||||
BString _SolutionElementText(
|
||||
const BSolverProblemSolutionElement*
|
||||
element) const;
|
||||
bool _AnySolutionSelected() const;
|
||||
|
||||
private:
|
||||
sem_id fDoneSemaphore;
|
||||
bool fClientWaiting;
|
||||
bool fAccepted;
|
||||
BGroupView* fContainerView;
|
||||
BButton* fCancelButton;
|
||||
BButton* fRetryButton;
|
||||
SolutionMap fSolutions;
|
||||
const SolverPackageSet* fPackagesAddedByUser;
|
||||
const SolverPackageSet* fPackagesRemovedByUser;
|
||||
};
|
||||
|
||||
|
||||
#endif // PROBLEM_WINDOW_H
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "Root.h"
|
||||
|
||||
#include <Alert.h>
|
||||
#include <Directory.h>
|
||||
#include <Entry.h>
|
||||
#include <package/PackageDefs.h>
|
||||
@ -16,13 +17,17 @@
|
||||
|
||||
#include <AutoDeleter.h>
|
||||
#include <AutoLocker.h>
|
||||
#include <Server.h>
|
||||
|
||||
#include <package/DaemonDefs.h>
|
||||
#include <package/manager/Exceptions.h>
|
||||
|
||||
#include "DebugSupport.h"
|
||||
#include "PackageManager.h"
|
||||
|
||||
|
||||
using namespace BPackageKit::BPrivate;
|
||||
using namespace BPackageKit::BManager::BPrivate;
|
||||
|
||||
|
||||
// #pragma mark - VolumeJob
|
||||
@ -215,6 +220,22 @@ Root::FindVolume(dev_t deviceID) const
|
||||
}
|
||||
|
||||
|
||||
Volume*
|
||||
Root::GetVolume(BPackageInstallationLocation location)
|
||||
{
|
||||
switch ((BPackageInstallationLocation)location) {
|
||||
case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM:
|
||||
return fSystemVolume;
|
||||
case B_PACKAGE_INSTALLATION_LOCATION_COMMON:
|
||||
return fCommonVolume;
|
||||
case B_PACKAGE_INSTALLATION_LOCATION_HOME:
|
||||
return fHomeVolume;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Root::HandleRequest(BMessage* message)
|
||||
{
|
||||
@ -259,22 +280,6 @@ Root::_GetVolume(PackageFSMountType mountType)
|
||||
}
|
||||
|
||||
|
||||
Volume*
|
||||
Root::_GetVolume(BPackageInstallationLocation location)
|
||||
{
|
||||
switch ((BPackageInstallationLocation)location) {
|
||||
case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM:
|
||||
return fSystemVolume;
|
||||
case B_PACKAGE_INSTALLATION_LOCATION_COMMON:
|
||||
return fCommonVolume;
|
||||
case B_PACKAGE_INSTALLATION_LOCATION_HOME:
|
||||
return fHomeVolume;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Volume*
|
||||
Root::_NextVolumeFor(Volume* volume)
|
||||
{
|
||||
@ -330,8 +335,39 @@ Root::_ProcessNodeMonitorEvents(Volume* volume)
|
||||
{
|
||||
volume->ProcessPendingNodeMonitorEvents();
|
||||
|
||||
if (volume->HasPendingPackageActivationChanges())
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -347,7 +383,7 @@ Root::_HandleRequest(BMessage* message)
|
||||
|
||||
// get the volume and let it handle the message
|
||||
AutoLocker<BLocker> locker(fLock);
|
||||
Volume* volume = _GetVolume((BPackageInstallationLocation)location);
|
||||
Volume* volume = GetVolume((BPackageInstallationLocation)location);
|
||||
locker.Unlock();
|
||||
|
||||
if (volume != NULL) {
|
||||
@ -396,3 +432,21 @@ Root::_JobRunner()
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ public:
|
||||
// deletes the volume (eventually)
|
||||
|
||||
Volume* FindVolume(dev_t deviceID) const;
|
||||
Volume* GetVolume(
|
||||
BPackageInstallationLocation location);
|
||||
|
||||
void HandleRequest(BMessage* message);
|
||||
|
||||
@ -60,8 +62,6 @@ private:
|
||||
|
||||
private:
|
||||
Volume** _GetVolume(PackageFSMountType mountType);
|
||||
Volume* _GetVolume(
|
||||
BPackageInstallationLocation location);
|
||||
Volume* _NextVolumeFor(Volume* volume);
|
||||
|
||||
void _InitPackages(Volume* volume);
|
||||
@ -74,6 +74,8 @@ private:
|
||||
static status_t _JobRunnerEntry(void* data);
|
||||
status_t _JobRunner();
|
||||
|
||||
static void _ShowError(const char* errorMessage);
|
||||
|
||||
private:
|
||||
mutable BLocker fLock;
|
||||
node_ref fNodeRef;
|
||||
|
@ -249,30 +249,47 @@ private:
|
||||
|
||||
|
||||
struct Volume::CommitTransactionHandler {
|
||||
CommitTransactionHandler(Volume* volume, BMessage* request, BMessage& reply)
|
||||
CommitTransactionHandler(Volume* volume,
|
||||
const PackageSet& packagesAlreadyAdded,
|
||||
const PackageSet& packagesAlreadyRemoved)
|
||||
:
|
||||
fVolume(volume),
|
||||
fRequest(request),
|
||||
fReply(reply),
|
||||
fPackagesToActivate(20, true),
|
||||
fPackagesToDeactivate(),
|
||||
fAddedPackages(),
|
||||
fRemovedPackages()
|
||||
fRemovedPackages(),
|
||||
fPackagesAlreadyAdded(packagesAlreadyAdded),
|
||||
fPackagesAlreadyRemoved(packagesAlreadyRemoved)
|
||||
{
|
||||
}
|
||||
|
||||
void HandleRequest()
|
||||
void HandleRequest(BMessage* request, BMessage* reply)
|
||||
{
|
||||
status_t error;
|
||||
BActivationTransaction transaction(request, &error);
|
||||
if (error == B_OK)
|
||||
error = transaction.InitCheck();
|
||||
if (error != B_OK) {
|
||||
if (error == B_NO_MEMORY)
|
||||
throw Exception(B_NO_MEMORY);
|
||||
throw Exception(B_DAEMON_BAD_REQUEST);
|
||||
}
|
||||
|
||||
HandleRequest(transaction, reply);
|
||||
}
|
||||
|
||||
void HandleRequest(const BActivationTransaction& transaction,
|
||||
BMessage* reply)
|
||||
{
|
||||
// check the change count
|
||||
int64 changeCount;
|
||||
if (fRequest->FindInt64("change count", &changeCount) != B_OK)
|
||||
if (transaction.ChangeCount() != fVolume->fChangeCount)
|
||||
throw Exception(B_DAEMON_CHANGE_COUNT_MISMATCH);
|
||||
|
||||
// collect the packages to deactivate
|
||||
_GetPackagesToDeactivate();
|
||||
_GetPackagesToDeactivate(transaction);
|
||||
|
||||
// read the packages to activate
|
||||
_ReadPackagesToActivate();
|
||||
_ReadPackagesToActivate(transaction);
|
||||
|
||||
// anything to do at all?
|
||||
if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) {
|
||||
@ -281,7 +298,7 @@ struct Volume::CommitTransactionHandler {
|
||||
}
|
||||
|
||||
// create an old state directory
|
||||
_CreateOldStateDirectory();
|
||||
_CreateOldStateDirectory(reply);
|
||||
|
||||
// move packages to deactivate to old state directory
|
||||
_RemovePackagesToDeactivate();
|
||||
@ -311,29 +328,25 @@ struct Volume::CommitTransactionHandler {
|
||||
_RemoveOldStateDirectory();
|
||||
}
|
||||
|
||||
const BString& OldStateDirectoryName() const
|
||||
{
|
||||
return fOldStateDirectoryName;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef BObjectList<Package> PackageList;
|
||||
|
||||
void _GetPackagesToDeactivate()
|
||||
void _GetPackagesToDeactivate(const BActivationTransaction& transaction)
|
||||
{
|
||||
static const char* const kPackagesToDeactivateFieldName = "deactivate";
|
||||
|
||||
// get the number of packages to activate
|
||||
type_code type;
|
||||
int32 packagesToDeactivateCount;
|
||||
if (fRequest->GetInfo(kPackagesToDeactivateFieldName, &type,
|
||||
&packagesToDeactivateCount) != B_OK) {
|
||||
// the field is missing, i.e. no packages shall be deactivated
|
||||
// get the number of packages to deactivate
|
||||
const BStringList& packagesToDeactivate
|
||||
= transaction.PackagesToDeactivate();
|
||||
int32 packagesToDeactivateCount = packagesToDeactivate.CountStrings();
|
||||
if (packagesToDeactivateCount == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < packagesToDeactivateCount; i++) {
|
||||
const char* packageName;
|
||||
status_t error = fRequest->FindString(
|
||||
kPackagesToDeactivateFieldName, i, &packageName);
|
||||
if (error != B_OK)
|
||||
throw Exception(error);
|
||||
|
||||
BString packageName = packagesToDeactivate.StringAt(i);
|
||||
Package* package = fVolume->fPackagesByFileName.Lookup(packageName);
|
||||
if (package == NULL) {
|
||||
throw Exception(B_DAEMON_NO_SUCH_PACKAGE, "no such package",
|
||||
@ -346,30 +359,19 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void _ReadPackagesToActivate()
|
||||
void _ReadPackagesToActivate(const BActivationTransaction& transaction)
|
||||
{
|
||||
static const char* const kPackagesToActivateFieldName = "activate";
|
||||
|
||||
// get the number of packages to activate
|
||||
type_code type;
|
||||
int32 packagesToActivateCount;
|
||||
if (fRequest->GetInfo(kPackagesToActivateFieldName, &type,
|
||||
&packagesToActivateCount) != B_OK) {
|
||||
// the field is missing, i.e. no packages shall be activated
|
||||
const BStringList& packagesToActivate
|
||||
= transaction.PackagesToActivate();
|
||||
int32 packagesToActivateCount = packagesToActivate.CountStrings();
|
||||
if (packagesToActivateCount == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// get the name of the transaction directory
|
||||
BString transactionDirectoryName;
|
||||
if (packagesToActivateCount > 0) {
|
||||
if (fRequest->FindString("transaction", &transactionDirectoryName)
|
||||
!= B_OK) {
|
||||
throw Exception(B_DAEMON_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
// check the name -- we only allow a simple subdirectory of the admin
|
||||
// directory
|
||||
// check the transaction directory name -- we only allow a simple
|
||||
// subdirectory of the admin directory
|
||||
const BString& transactionDirectoryName
|
||||
= transaction.TransactionDirectoryName();
|
||||
if (transactionDirectoryName.IsEmpty()
|
||||
|| transactionDirectoryName.FindFirst('/') >= 0
|
||||
|| transactionDirectoryName == "."
|
||||
@ -394,19 +396,23 @@ private:
|
||||
|
||||
// read the packages
|
||||
for (int32 i = 0; i < packagesToActivateCount; i++) {
|
||||
const char* packageName;
|
||||
error = fRequest->FindString(kPackagesToActivateFieldName, i,
|
||||
&packageName);
|
||||
if (error != B_OK)
|
||||
throw Exception(error);
|
||||
BString packageName = packagesToActivate.StringAt(i);
|
||||
|
||||
// make sure it doesn't clash with an already existing package
|
||||
Package* package = fVolume->fPackagesByFileName.Lookup(packageName);
|
||||
if (package != NULL
|
||||
&& fPackagesToDeactivate.find(package)
|
||||
== fPackagesToDeactivate.end()) {
|
||||
throw Exception(B_DAEMON_PACKAGE_ALREADY_EXISTS, NULL,
|
||||
packageName);
|
||||
if (package != NULL) {
|
||||
if (fPackagesAlreadyAdded.find(package)
|
||||
!= fPackagesAlreadyAdded.end()) {
|
||||
if (!fPackagesToActivate.AddItem(package))
|
||||
throw Exception(B_NO_MEMORY);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fPackagesToDeactivate.find(package)
|
||||
== fPackagesToDeactivate.end()) {
|
||||
throw Exception(B_DAEMON_PACKAGE_ALREADY_EXISTS, NULL,
|
||||
packageName);
|
||||
}
|
||||
}
|
||||
|
||||
// read the package
|
||||
@ -430,7 +436,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void _CreateOldStateDirectory()
|
||||
void _CreateOldStateDirectory(BMessage* reply)
|
||||
{
|
||||
// construct a nice name from the current date and time
|
||||
time_t nowSeconds = time(NULL);
|
||||
@ -478,9 +484,11 @@ private:
|
||||
throw Exception(error, "failed to write old activation file");
|
||||
|
||||
// add the old state directory to the reply
|
||||
error = fReply.AddString("old state", fOldStateDirectoryName);
|
||||
if (error != B_OK)
|
||||
throw Exception(error, "failed to add field to reply");
|
||||
if (reply != NULL) {
|
||||
error = reply->AddString("old state", fOldStateDirectoryName);
|
||||
if (error != B_OK)
|
||||
throw Exception(error, "failed to add field to reply");
|
||||
}
|
||||
}
|
||||
|
||||
void _RemovePackagesToDeactivate()
|
||||
@ -490,8 +498,14 @@ private:
|
||||
|
||||
for (PackageSet::const_iterator it = fPackagesToDeactivate.begin();
|
||||
it != fPackagesToDeactivate.end(); ++it) {
|
||||
// get an BEntry for the package
|
||||
Package* package = *it;
|
||||
if (fPackagesAlreadyRemoved.find(package)
|
||||
!= fPackagesAlreadyRemoved.end()) {
|
||||
fRemovedPackages.insert(package);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get a BEntry for the package
|
||||
entry_ref entryRef;
|
||||
entryRef.device = fVolume->fPackagesDirectoryRef.device;
|
||||
entryRef.directory = fVolume->fPackagesDirectoryRef.node;
|
||||
@ -532,8 +546,14 @@ private:
|
||||
|
||||
int32 count = fPackagesToActivate.CountItems();
|
||||
for (int32 i = 0; i < count; i++) {
|
||||
// get an BEntry for the package
|
||||
Package* package = fPackagesToActivate.ItemAt(i);
|
||||
if (fPackagesAlreadyAdded.find(package)
|
||||
!= fPackagesAlreadyAdded.end()) {
|
||||
fAddedPackages.insert(package);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get a BEntry for the package
|
||||
entry_ref entryRef;
|
||||
entryRef.device = fTransactionDirectoryRef.device;
|
||||
entryRef.directory = fTransactionDirectoryRef.node;
|
||||
@ -582,6 +602,11 @@ private:
|
||||
Package* package = *it;
|
||||
fVolume->_RemovePackage(package);
|
||||
|
||||
if (fPackagesAlreadyAdded.find(package)
|
||||
!= fPackagesAlreadyAdded.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (transactionDirectory.InitCheck() != B_OK)
|
||||
continue;
|
||||
|
||||
@ -631,8 +656,13 @@ private:
|
||||
|
||||
for (PackageSet::iterator it = fRemovedPackages.begin();
|
||||
it != fRemovedPackages.end(); ++it) {
|
||||
// get an BEntry for the package
|
||||
Package* package = *it;
|
||||
if (fPackagesAlreadyRemoved.find(package)
|
||||
!= fPackagesAlreadyRemoved.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get a BEntry for the package
|
||||
BEntry entry;
|
||||
status_t error = entry.SetTo(&fOldStateDirectory,
|
||||
package->FileName());
|
||||
@ -681,16 +711,16 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
Volume* fVolume;
|
||||
BMessage* fRequest;
|
||||
BMessage& fReply;
|
||||
PackageList fPackagesToActivate;
|
||||
PackageSet fPackagesToDeactivate;
|
||||
PackageSet fAddedPackages;
|
||||
PackageSet fRemovedPackages;
|
||||
BDirectory fOldStateDirectory;
|
||||
BString fOldStateDirectoryName;
|
||||
node_ref fTransactionDirectoryRef;
|
||||
Volume* fVolume;
|
||||
PackageList fPackagesToActivate;
|
||||
PackageSet fPackagesToDeactivate;
|
||||
PackageSet fAddedPackages;
|
||||
PackageSet fRemovedPackages;
|
||||
const PackageSet& fPackagesAlreadyAdded;
|
||||
const PackageSet& fPackagesAlreadyRemoved;
|
||||
BDirectory fOldStateDirectory;
|
||||
BString fOldStateDirectoryName;
|
||||
node_ref fTransactionDirectoryRef;
|
||||
};
|
||||
|
||||
|
||||
@ -710,6 +740,7 @@ Volume::Volume(BLooper* looper)
|
||||
fPackagesByNodeRef(),
|
||||
fPendingNodeMonitorEventsLock("pending node monitor events"),
|
||||
fPendingNodeMonitorEvents(),
|
||||
fNodeMonitorEventHandleTime(0),
|
||||
fPackagesToBeActivated(),
|
||||
fPackagesToBeDeactivated(),
|
||||
fChangeCount(0),
|
||||
@ -1007,10 +1038,10 @@ Volume::HandleCommitTransactionRequest(BMessage* message)
|
||||
return;
|
||||
|
||||
// perform the request
|
||||
CommitTransactionHandler handler(this, message, reply);
|
||||
CommitTransactionHandler handler(this, PackageSet(), PackageSet());
|
||||
int32 error;
|
||||
try {
|
||||
handler.HandleRequest();
|
||||
handler.HandleRequest(message, &reply);
|
||||
error = B_DAEMON_OK;
|
||||
} catch (Exception& exception) {
|
||||
error = exception.Error();
|
||||
@ -1086,6 +1117,23 @@ Volume::MessageReceived(BMessage* message)
|
||||
}
|
||||
|
||||
|
||||
BPackageInstallationLocation
|
||||
Volume::Location() const
|
||||
{
|
||||
switch (fMountType) {
|
||||
case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
|
||||
return B_PACKAGE_INSTALLATION_LOCATION_SYSTEM;
|
||||
case PACKAGE_FS_MOUNT_TYPE_COMMON:
|
||||
return B_PACKAGE_INSTALLATION_LOCATION_COMMON;
|
||||
case PACKAGE_FS_MOUNT_TYPE_HOME:
|
||||
return B_PACKAGE_INSTALLATION_LOCATION_HOME;
|
||||
case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
|
||||
default:
|
||||
return B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
Volume::OpenRootDirectory() const
|
||||
{
|
||||
@ -1150,6 +1198,86 @@ Volume::ProcessPendingPackageActivationChanges()
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Volume::ClearPackageActivationChanges()
|
||||
{
|
||||
fPackagesToBeActivated.clear();
|
||||
fPackagesToBeDeactivated.clear();
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Volume::CreateTransaction(BPackageInstallationLocation location,
|
||||
BActivationTransaction& _transaction, BDirectory& _transactionDirectory)
|
||||
{
|
||||
// open admin directory
|
||||
BDirectory adminDirectory;
|
||||
status_t error = _OpenPackagesSubDirectory(
|
||||
RelativePath(kAdminDirectoryName), true, adminDirectory);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// create a transaction directory
|
||||
int uniqueId = 1;
|
||||
BString directoryName;
|
||||
for (;; uniqueId++) {
|
||||
directoryName.SetToFormat("transaction-%d", uniqueId);
|
||||
if (directoryName.IsEmpty())
|
||||
return B_NO_MEMORY;
|
||||
|
||||
error = adminDirectory.CreateDirectory(directoryName,
|
||||
&_transactionDirectory);
|
||||
if (error == B_OK)
|
||||
break;
|
||||
if (error != B_FILE_EXISTS)
|
||||
return error;
|
||||
}
|
||||
|
||||
// init the transaction
|
||||
error = _transaction.SetTo(location, fChangeCount, directoryName);
|
||||
if (error != B_OK) {
|
||||
BEntry entry;
|
||||
_transactionDirectory.GetEntry(&entry);
|
||||
_transactionDirectory.Unset();
|
||||
if (entry.InitCheck() == B_OK)
|
||||
entry.Remove();
|
||||
return error;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Volume::CommitTransaction(const BActivationTransaction& transaction,
|
||||
const PackageSet& packagesAlreadyAdded,
|
||||
const PackageSet& packagesAlreadyRemoved,
|
||||
BDaemonClient::BCommitTransactionResult& _result)
|
||||
{
|
||||
// perform the request
|
||||
CommitTransactionHandler handler(this, packagesAlreadyAdded,
|
||||
packagesAlreadyRemoved);
|
||||
int32 error;
|
||||
try {
|
||||
handler.HandleRequest(transaction, NULL);
|
||||
error = B_DAEMON_OK;
|
||||
_result.SetTo(error, BString(), BString(),
|
||||
handler.OldStateDirectoryName());
|
||||
} catch (Exception& exception) {
|
||||
error = exception.Error();
|
||||
_result.SetTo(error, exception.ErrorMessage(), exception.PackageName(),
|
||||
BString());
|
||||
} catch (std::bad_alloc& exception) {
|
||||
error = B_NO_MEMORY;
|
||||
_result.SetTo(error, BString(), BString(), BString());
|
||||
}
|
||||
|
||||
// revert on error
|
||||
if (error != B_DAEMON_OK)
|
||||
handler.Revert();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Volume::_HandleEntryCreatedOrRemoved(const BMessage* message, bool created)
|
||||
{
|
||||
|
@ -9,19 +9,22 @@
|
||||
#define VOLUME_H
|
||||
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <Handler.h>
|
||||
#include <Locker.h>
|
||||
#include <Message.h>
|
||||
#include <String.h>
|
||||
|
||||
#include <package/ActivationTransaction.h>
|
||||
#include <package/DaemonClient.h>
|
||||
#include <package/packagefs.h>
|
||||
#include <util/DoublyLinkedList.h>
|
||||
|
||||
#include "Package.h"
|
||||
|
||||
|
||||
using BPackageKit::BPrivate::BActivationTransaction;
|
||||
using BPackageKit::BPrivate::BDaemonClient;
|
||||
|
||||
class BDirectory;
|
||||
|
||||
class Root;
|
||||
@ -31,6 +34,10 @@ namespace BPackageKit {
|
||||
class BSolverRepository;
|
||||
}
|
||||
|
||||
using BPackageKit::BPackageInstallationLocation;
|
||||
using BPackageKit::BSolver;
|
||||
using BPackageKit::BSolverRepository;
|
||||
|
||||
|
||||
class Volume : public BHandler {
|
||||
public:
|
||||
@ -61,6 +68,7 @@ public:
|
||||
{ return fPath; }
|
||||
PackageFSMountType MountType() const
|
||||
{ return fMountType; }
|
||||
BPackageInstallationLocation Location() const;
|
||||
|
||||
const node_ref& RootDirectoryRef() const
|
||||
{ return fRootDirectoryRef; }
|
||||
@ -81,12 +89,33 @@ public:
|
||||
void SetRoot(Root* root)
|
||||
{ fRoot = root; }
|
||||
|
||||
const PackageFileNameHashTable& PackagesByFileName() const
|
||||
{ return fPackagesByFileName; }
|
||||
const PackageNodeRefHashTable& PackagesByNodeRef() const
|
||||
{ return fPackagesByNodeRef; }
|
||||
|
||||
int OpenRootDirectory() const;
|
||||
|
||||
void ProcessPendingNodeMonitorEvents();
|
||||
|
||||
bool HasPendingPackageActivationChanges() const;
|
||||
void ProcessPendingPackageActivationChanges();
|
||||
void ClearPackageActivationChanges();
|
||||
const PackageSet& PackagesToBeActivated() const
|
||||
{ return fPackagesToBeActivated; }
|
||||
const PackageSet& PackagesToBeDeactivated() const
|
||||
{ return fPackagesToBeDeactivated; }
|
||||
|
||||
status_t CreateTransaction(
|
||||
BPackageInstallationLocation location,
|
||||
BActivationTransaction& _transaction,
|
||||
BDirectory& _transactionDirectory);
|
||||
void CommitTransaction(
|
||||
const BActivationTransaction& transaction,
|
||||
const PackageSet& packagesAlreadyAdded,
|
||||
const PackageSet& packagesAlreadyRemoved,
|
||||
BDaemonClient::BCommitTransactionResult&
|
||||
_result);
|
||||
|
||||
private:
|
||||
struct NodeMonitorEvent;
|
||||
@ -97,7 +126,6 @@ private:
|
||||
friend struct CommitTransactionHandler;
|
||||
|
||||
typedef DoublyLinkedList<NodeMonitorEvent> NodeMonitorEventList;
|
||||
typedef std::set<Package*> PackageSet;
|
||||
|
||||
private:
|
||||
void _HandleEntryCreatedOrRemoved(
|
||||
@ -164,6 +192,7 @@ private:
|
||||
PackageNodeRefHashTable fPackagesByNodeRef;
|
||||
BLocker fPendingNodeMonitorEventsLock;
|
||||
NodeMonitorEventList fPendingNodeMonitorEvents;
|
||||
bigtime_t fNodeMonitorEventHandleTime;
|
||||
PackageSet fPackagesToBeActivated;
|
||||
PackageSet fPackagesToBeDeactivated;
|
||||
int64 fChangeCount;
|
||||
|
Loading…
Reference in New Issue
Block a user