diff --git a/src/servers/package/Jamfile b/src/servers/package/Jamfile index 4125ab17ba..348dc1b6df 100644 --- a/src/servers/package/Jamfile +++ b/src/servers/package/Jamfile @@ -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 -; + ; diff --git a/src/servers/package/Package.h b/src/servers/package/Package.h index e2ac3dd7f0..4fe0324a5a 100644 --- a/src/servers/package/Package.h +++ b/src/servers/package/Package.h @@ -9,6 +9,8 @@ #define PACKAGE_H +#include + #include #include #include @@ -124,6 +126,7 @@ struct PackageNodeRefHashDefinition { typedef BOpenHashTable PackageFileNameHashTable; typedef BOpenHashTable PackageNodeRefHashTable; +typedef std::set PackageSet; #endif // PACKAGE_H diff --git a/src/servers/package/PackageManager.cpp b/src/servers/package/PackageManager.cpp new file mode 100644 index 0000000000..7696b05dae --- /dev/null +++ b/src/servers/package/PackageManager.cpp @@ -0,0 +1,450 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "PackageManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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(be_app); + if (server == NULL || server->InitGUIContext() != B_OK) + throw BFatalErrorException("failed to initialize the GUI"); +} diff --git a/src/servers/package/PackageManager.h b/src/servers/package/PackageManager.h new file mode 100644 index 0000000000..4737b7a451 --- /dev/null +++ b/src/servers/package/PackageManager.h @@ -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 +#include + +#include +#include + +#include +#include + + +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 SolverPackageSet; + typedef std::map 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 diff --git a/src/servers/package/ProblemWindow.cpp b/src/servers/package/ProblemWindow.cpp new file mode 100644 index 0000000000..db0493a9f6 --- /dev/null +++ b/src/servers/package/ProblemWindow.cpp @@ -0,0 +1,287 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "ProblemWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +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 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; +} diff --git a/src/servers/package/ProblemWindow.h b/src/servers/package/ProblemWindow.h new file mode 100644 index 0000000000..3ae3ef2904 --- /dev/null +++ b/src/servers/package/ProblemWindow.h @@ -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 +#include + +#include + + +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 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 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 diff --git a/src/servers/package/Root.cpp b/src/servers/package/Root.cpp index ebc3e7d26c..8c3c3c0928 100644 --- a/src/servers/package/Root.cpp +++ b/src/servers/package/Root.cpp @@ -9,6 +9,7 @@ #include "Root.h" +#include #include #include #include @@ -16,13 +17,17 @@ #include #include +#include #include +#include #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 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(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); +} diff --git a/src/servers/package/Root.h b/src/servers/package/Root.h index 470eb1e9e5..de4bde38b2 100644 --- a/src/servers/package/Root.h +++ b/src/servers/package/Root.h @@ -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; diff --git a/src/servers/package/Volume.cpp b/src/servers/package/Volume.cpp index 9a69fcdb00..83c6432fd6 100644 --- a/src/servers/package/Volume.cpp +++ b/src/servers/package/Volume.cpp @@ -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 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) { diff --git a/src/servers/package/Volume.h b/src/servers/package/Volume.h index 51b644b78b..2508060c91 100644 --- a/src/servers/package/Volume.h +++ b/src/servers/package/Volume.h @@ -9,19 +9,22 @@ #define VOLUME_H -#include - #include #include #include #include +#include +#include #include #include #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 NodeMonitorEventList; - typedef std::set PackageSet; private: void _HandleEntryCreatedOrRemoved( @@ -164,6 +192,7 @@ private: PackageNodeRefHashTable fPackagesByNodeRef; BLocker fPendingNodeMonitorEventsLock; NodeMonitorEventList fPendingNodeMonitorEvents; + bigtime_t fNodeMonitorEventHandleTime; PackageSet fPackagesToBeActivated; PackageSet fPackagesToBeDeactivated; int64 fChangeCount;