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