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