xref: /haiku/src/servers/package/PackageManager.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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 
31 using BPackageKit::BManager::BPrivate::BAbortedByUserException;
32 using BPackageKit::BManager::BPrivate::BFatalErrorException;
33 using BPackageKit::BManager::BPrivate::BRepositoryBuilder;
34 
35 
36 PackageManager::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 
50 PackageManager::~PackageManager()
51 {
52 	if (fProblemWindow != NULL)
53 		fProblemWindow->PostMessage(B_QUIT_REQUESTED);
54 }
55 
56 
57 void
58 PackageManager::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 
130 void
131 PackageManager::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 
151 void
152 PackageManager::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 
200 status_t
201 PackageManager::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 
213 status_t
214 PackageManager::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 
255 void
256 PackageManager::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 
270 void
271 PackageManager::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 
299 void
300 PackageManager::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 
319 void
320 PackageManager::ProgressPackageDownloadStarted(const char* packageName)
321 {
322 }
323 
324 
325 void
326 PackageManager::ProgressPackageDownloadActive(const char* packageName,
327 	float completionPercentage, off_t bytes, off_t totalBytes)
328 {
329 }
330 
331 
332 void
333 PackageManager::ProgressPackageDownloadComplete(const char* packageName)
334 {
335 }
336 
337 
338 void
339 PackageManager::ProgressPackageChecksumStarted(const char* title)
340 {
341 }
342 
343 
344 void
345 PackageManager::ProgressPackageChecksumComplete(const char* title)
346 {
347 }
348 
349 
350 void
351 PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
352 {
353 }
354 
355 
356 void
357 PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
358 	const BCommitTransactionResult& result)
359 {
360 }
361 
362 
363 void
364 PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
365 {
366 }
367 
368 
369 void
370 PackageManager::JobFailed(BSupportKit::BJob* job)
371 {
372 // TODO:...
373 }
374 
375 
376 void
377 PackageManager::JobAborted(BSupportKit::BJob* job)
378 {
379 // TODO:...
380 }
381 
382 
383 bool
384 PackageManager::_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 
396 BSolverPackage*
397 PackageManager::_SolverPackageFor(Package* package) const
398 {
399 	SolverPackageMap::const_iterator it = fSolverPackages.find(package);
400 	return it != fSolverPackages.end() ? it->second : NULL;
401 }
402 
403 
404 void
405 PackageManager::_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