1/*
2 * Copyright 2013-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "PackageManager.h"
8
9#include <Catalog.h>
10#include <Notification.h>
11#include <package/DownloadFileRequest.h>
12#include <package/RefreshRepositoryRequest.h>
13#include <package/solver/SolverPackage.h>
14#include <package/solver/SolverPackageSpecifierList.h>
15#include <package/solver/SolverProblem.h>
16#include <package/solver/SolverProblemSolution.h>
17
18#include <AutoDeleter.h>
19#include <package/manager/Exceptions.h>
20#include <package/manager/RepositoryBuilder.h>
21#include <Server.h>
22
23#include "ProblemWindow.h"
24#include "ResultWindow.h"
25#include "Root.h"
26#include "Volume.h"
27
28#undef B_TRANSLATION_CONTEXT
29#define B_TRANSLATION_CONTEXT "PackageManager"
30
31using BPackageKit::BManager::BPrivate::BAbortedByUserException;
32using BPackageKit::BManager::BPrivate::BFatalErrorException;
33using BPackageKit::BManager::BPrivate::BRepositoryBuilder;
34
35
36PackageManager::PackageManager(Root* root, Volume* volume)
37	:
38	BPackageManager(volume->Location(), this, this),
39	BPackageManager::UserInteractionHandler(),
40	fRoot(root),
41	fVolume(volume),
42	fSolverPackages(),
43	fPackagesAddedByUser(),
44	fPackagesRemovedByUser(),
45	fProblemWindow(NULL)
46{
47}
48
49
50PackageManager::~PackageManager()
51{
52	if (fProblemWindow != NULL)
53		fProblemWindow->PostMessage(B_QUIT_REQUESTED);
54}
55
56
57void
58PackageManager::HandleUserChanges()
59{
60	const PackageSet& packagesToActivate = fVolume->PackagesToBeActivated();
61	const PackageSet& packagesToDeactivate = fVolume->PackagesToBeDeactivated();
62
63	if (packagesToActivate.empty() && packagesToDeactivate.empty())
64		return;
65
66	if (packagesToActivate.empty()) {
67		// only packages removed -- use uninstall mode
68		Init(B_ADD_INSTALLED_REPOSITORIES);
69
70		BSolverPackageSpecifierList packagesToUninstall;
71		for (PackageSet::const_iterator it = packagesToDeactivate.begin();
72			it != packagesToDeactivate.end(); ++it) {
73			BSolverPackage* solverPackage = _SolverPackageFor(*it);
74			if (solverPackage == NULL)
75				continue;
76
77			if (!packagesToUninstall.AppendSpecifier(solverPackage))
78				throw std::bad_alloc();
79			fPackagesRemovedByUser.insert(solverPackage);
80		}
81
82		if (fPackagesRemovedByUser.empty())
83			return;
84
85		Uninstall(packagesToUninstall);
86	} else {
87		// packages added and (possibly) remove -- manually add/remove those
88		// from the repository and use verify mode
89		Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
90			| B_REFRESH_REPOSITORIES);
91
92		// disable and remove uninstalled packages
93		InstalledRepository& repository = InstallationRepository();
94		for (PackageSet::const_iterator it = packagesToDeactivate.begin();
95			it != packagesToDeactivate.end(); ++it) {
96			BSolverPackage* solverPackage = _SolverPackageFor(*it);
97			if (solverPackage == NULL)
98				continue;
99
100			repository.DisablePackage(solverPackage);
101			if (!repository.PackagesToDeactivate().AddItem(solverPackage))
102				throw std::bad_alloc();
103			fPackagesRemovedByUser.insert(solverPackage);
104		}
105
106		// add new packages
107		BRepositoryBuilder repositoryBuilder(repository);
108		for (PackageSet::const_iterator it = packagesToActivate.begin();
109			it != packagesToActivate.end(); ++it) {
110			Package* package = *it;
111			BSolverPackage* solverPackage;
112			repositoryBuilder.AddPackage(package->Info(), NULL, &solverPackage);
113			fSolverPackages[package] = solverPackage;
114			if (!repository.PackagesToActivate().AddItem(solverPackage))
115				throw std::bad_alloc();
116			fPackagesAddedByUser.insert(solverPackage);
117		}
118
119		if (fPackagesRemovedByUser.empty() && fPackagesAddedByUser.empty())
120			return;
121
122// TODO: When packages are moved out of the packages directory, we can't create
123// a complete "old-state" directory.
124
125		VerifyInstallation();
126	}
127}
128
129
130void
131PackageManager::InitInstalledRepository(InstalledRepository& repository)
132{
133	const char* name = repository.InitialName();
134	BRepositoryBuilder repositoryBuilder(repository, name);
135
136	if (Volume* volume = fRoot->GetVolume(repository.Location())) {
137		for (PackageFileNameHashTable::Iterator it
138				= volume->PackagesByFileNameIterator(); it.HasNext();) {
139			Package* package = it.Next();
140			if (package->IsActive()) {
141				BSolverPackage* solverPackage;
142				repositoryBuilder.AddPackage(package->Info(), NULL,
143					&solverPackage);
144				fSolverPackages[package] = solverPackage;
145			}
146		}
147	}
148}
149
150
151void
152PackageManager::ResultComputed(InstalledRepository& repository)
153{
154	// Normalize the result, i.e. remove the packages added by the user which
155	// have been removed again in the problem resolution process, and vice
156	// versa.
157	if (repository.Location() != fVolume->Location())
158		return;
159
160	PackageList& packagesToActivate = repository.PackagesToActivate();
161	PackageList& packagesToDeactivate = repository.PackagesToDeactivate();
162
163	for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i);
164		i++) {
165		if (fPackagesAddedByUser.erase(package) == 0)
166			continue;
167
168		for (SolverPackageMap::iterator it = fSolverPackages.begin();
169			it != fSolverPackages.end(); ++it) {
170			if (it->second == package) {
171				fSolverPackages.erase(it);
172				break;
173			}
174		}
175
176		repository.EnablePackage(package);
177		packagesToDeactivate.RemoveItemAt(i--);
178		packagesToActivate.RemoveItem(package);
179		repository.DeletePackage(package);
180	}
181
182	for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i);
183		i++) {
184		if (fPackagesRemovedByUser.erase(package) == 0)
185			continue;
186
187		repository.EnablePackage(package);
188		packagesToActivate.RemoveItemAt(i--);
189		packagesToDeactivate.RemoveItem(package);
190
191		// Note: We keep the package activated, but nonetheless it is gone,
192		// since the user has removed it from the packages directory. So unless
193		// the user moves it back, we won't find it upon next reboot.
194		// TODO: We probable even run into trouble when the user moves in a
195		// replacement afterward.
196	}
197}
198
199
200status_t
201PackageManager::PrepareTransaction(Transaction& transaction)
202{
203	Volume* volume = fRoot->GetVolume(transaction.Repository().Location());
204	if (volume == NULL)
205		return B_BAD_VALUE;
206
207	return volume->CreateTransaction(transaction.Repository().Location(),
208		transaction.ActivationTransaction(),
209		transaction.TransactionDirectory());
210}
211
212
213status_t
214PackageManager::CommitTransaction(Transaction& transaction,
215	BCommitTransactionResult& _result)
216{
217	Volume* volume = fRoot->GetVolume(transaction.Repository().Location());
218	if (volume == NULL)
219		return B_BAD_VALUE;
220
221	// Get the packages that have already been added to/removed from the
222	// packages directory and thus need to be handled specially by
223	// Volume::CommitTransaction().
224	PackageSet packagesAlreadyAdded;
225	PackageSet packagesAlreadyRemoved;
226	if (volume == fVolume) {
227		const PackageSet& packagesToActivate = volume->PackagesToBeActivated();
228		for (PackageSet::const_iterator it = packagesToActivate.begin();
229			it != packagesToActivate.end(); ++it) {
230			Package* package = *it;
231			if (fPackagesAddedByUser.find(_SolverPackageFor(package))
232					!= fPackagesAddedByUser.end()) {
233				packagesAlreadyAdded.insert(package);
234			}
235		}
236
237		const PackageSet& packagesToDeactivate
238			= volume->PackagesToBeDeactivated();
239		for (PackageSet::const_iterator it = packagesToDeactivate.begin();
240			it != packagesToDeactivate.end(); ++it) {
241			Package* package = *it;
242			if (fPackagesRemovedByUser.find(_SolverPackageFor(package))
243					!= fPackagesRemovedByUser.end()) {
244				packagesAlreadyRemoved.insert(package);
245			}
246		}
247	}
248
249	volume->CommitTransaction(transaction.ActivationTransaction(),
250		packagesAlreadyAdded, packagesAlreadyRemoved, _result);
251	return B_OK;
252}
253
254
255void
256PackageManager::HandleProblems()
257{
258	_InitGui();
259
260	if (fProblemWindow == NULL)
261		fProblemWindow = new ProblemWindow;
262
263	if (!fProblemWindow->Go(fSolver, fPackagesAddedByUser,
264			fPackagesRemovedByUser)) {
265		throw BAbortedByUserException();
266	}
267}
268
269
270void
271PackageManager::ConfirmChanges(bool fromMostSpecific)
272{
273	// Check whether there are any changes other than those made by the user.
274	_InitGui();
275	ResultWindow* window = new ResultWindow;
276	ObjectDeleter<ResultWindow> windowDeleter(window);
277
278	bool hasOtherChanges = false;
279	int32 count = fInstalledRepositories.CountItems();
280	if (fromMostSpecific) {
281		for (int32 i = count - 1; i >= 0; i--)
282			hasOtherChanges
283				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
284	} else {
285		for (int32 i = 0; i < count; i++)
286			hasOtherChanges
287				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
288	}
289
290	if (!hasOtherChanges)
291		return;
292
293	// show the window
294	if (windowDeleter.Detach()->Go() == 0)
295		throw BAbortedByUserException();
296}
297
298
299void
300PackageManager::Warn(status_t error, const char* format, ...)
301{
302	va_list args;
303	va_start(args, format);
304	BString message;
305	message.SetToFormatVarArgs(format, args);
306	va_end(args);
307
308	if (error != B_OK)
309		message << BString().SetToFormat(": %s", strerror(error));
310
311	BNotification notification(B_ERROR_NOTIFICATION);
312	notification.SetGroup(B_TRANSLATE("Package daemon"));
313	notification.SetTitle(B_TRANSLATE("Warning"));
314	notification.SetContent(message);
315	notification.Send();
316}
317
318
319void
320PackageManager::ProgressPackageDownloadStarted(const char* packageName)
321{
322}
323
324
325void
326PackageManager::ProgressPackageDownloadActive(const char* packageName,
327	float completionPercentage, off_t bytes, off_t totalBytes)
328{
329}
330
331
332void
333PackageManager::ProgressPackageDownloadComplete(const char* packageName)
334{
335}
336
337
338void
339PackageManager::ProgressPackageChecksumStarted(const char* title)
340{
341}
342
343
344void
345PackageManager::ProgressPackageChecksumComplete(const char* title)
346{
347}
348
349
350void
351PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
352{
353}
354
355
356void
357PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
358	const BCommitTransactionResult& result)
359{
360}
361
362
363void
364PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
365{
366}
367
368
369void
370PackageManager::JobFailed(BSupportKit::BJob* job)
371{
372// TODO:...
373}
374
375
376void
377PackageManager::JobAborted(BSupportKit::BJob* job)
378{
379// TODO:...
380}
381
382
383bool
384PackageManager::_AddResults(InstalledRepository& repository,
385	ResultWindow* window)
386{
387	if (!repository.HasChanges())
388		return false;
389
390	return window->AddLocationChanges(repository.Name(),
391		repository.PackagesToActivate(), fPackagesAddedByUser,
392		repository.PackagesToDeactivate(), fPackagesRemovedByUser);
393}
394
395
396BSolverPackage*
397PackageManager::_SolverPackageFor(Package* package) const
398{
399	SolverPackageMap::const_iterator it = fSolverPackages.find(package);
400	return it != fSolverPackages.end() ? it->second : NULL;
401}
402
403
404void
405PackageManager::_InitGui()
406{
407	BServer* server = dynamic_cast<BServer*>(be_app);
408	if (server == NULL || server->InitGUIContext() != B_OK)
409		throw BFatalErrorException("failed to initialize the GUI");
410}
411