xref: /haiku/src/apps/haikudepot/packagemanagement/PackageManager.cpp (revision 84cc2b9655c438c28cd0073991f41684d41d1fe8)
1 /*
2  * Copyright 2013-2021, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Ingo Weinhold <ingo_weinhold@gmx.de>
7  * 		Stephan Aßmus <superstippi@gmx.de>
8  * 		Rene Gollent <rene@gollent.com>
9  *		Julian Harnath <julian.harnath@rwth-aachen.de>
10  *		Andrew Lindesay <apl@lindesay.co.nz>
11  */
12 
13 
14 #include "PackageManager.h"
15 
16 #include <Alert.h>
17 #include <Catalog.h>
18 #include <Entry.h>
19 #include <FindDirectory.h>
20 #include <Path.h>
21 #include <Roster.h>
22 
23 #include <package/DownloadFileRequest.h>
24 #include <package/manager/Exceptions.h>
25 #include <package/RefreshRepositoryRequest.h>
26 #include <package/hpkg/NoErrorOutput.h>
27 #include <package/hpkg/PackageContentHandler.h>
28 #include <package/hpkg/PackageEntry.h>
29 #include <package/hpkg/PackageEntryAttribute.h>
30 #include <package/hpkg/PackageInfoAttributeValue.h>
31 #include <package/hpkg/PackageReader.h>
32 #include <package/solver/SolverPackage.h>
33 #include <package/solver/SolverProblem.h>
34 #include <package/solver/SolverProblemSolution.h>
35 
36 #include "AutoDeleter.h"
37 #include "AutoLocker.h"
38 #include "Logger.h"
39 #include "OpenPackageProcess.h"
40 #include "PackageInfo.h"
41 #include "ProblemWindow.h"
42 #include "ResultWindow.h"
43 
44 
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "PackageManager"
47 
48 
49 using namespace BPackageKit;
50 using namespace BPackageKit::BPrivate;
51 using namespace BPackageKit::BManager::BPrivate;
52 
53 using BPackageKit::BRefreshRepositoryRequest;
54 using BPackageKit::DownloadFileRequest;
55 using BPackageKit::BSolver;
56 using BPackageKit::BSolverPackage;
57 using BPackageKit::BSolverRepository;
58 using BPackageKit::BHPKG::BNoErrorOutput;
59 using BPackageKit::BHPKG::BPackageContentHandler;
60 using BPackageKit::BHPKG::BPackageEntry;
61 using BPackageKit::BHPKG::BPackageEntryAttribute;
62 using BPackageKit::BHPKG::BPackageInfoAttributeValue;
63 using BPackageKit::BHPKG::BPackageReader;
64 
65 
66 // #pragma mark - PackageProgressListener
67 
68 
69 PackageProgressListener::~PackageProgressListener()
70 {
71 }
72 
73 
74 void
75 PackageProgressListener::DownloadProgressChanged(const char* packageName,
76 	float progress)
77 {
78 }
79 
80 
81 void
82 PackageProgressListener::DownloadProgressComplete(const char* packageName)
83 {
84 }
85 
86 
87 void
88 PackageProgressListener::ConfirmedChanges(
89 	BPackageManager::InstalledRepository& repository)
90 {
91 }
92 
93 
94 void
95 PackageProgressListener::StartApplyingChanges(
96 	BPackageManager::InstalledRepository& repository)
97 {
98 }
99 
100 
101 void
102 PackageProgressListener::ApplyingChangesDone(
103 	BPackageManager::InstalledRepository& repository)
104 {
105 }
106 
107 
108 // #pragma mark - PackageManager
109 
110 
111 PackageManager::PackageManager(BPackageInstallationLocation location)
112 	:
113 	BPackageManager(location, &fClientInstallationInterface, this),
114 	BPackageManager::UserInteractionHandler(),
115 	fDecisionProvider(),
116 	fClientInstallationInterface(),
117 	fProblemWindow(NULL),
118 	fCurrentInstallPackage(NULL),
119 	fCurrentUninstallPackage(NULL)
120 {
121 }
122 
123 
124 PackageManager::~PackageManager()
125 {
126 	if (fProblemWindow != NULL)
127 		fProblemWindow->PostMessage(B_QUIT_REQUESTED);
128 }
129 
130 
131 PackageState
132 PackageManager::GetPackageState(const PackageInfo& package)
133 {
134 	// TODO: Fetch information from the PackageKit
135 	return NONE;
136 }
137 
138 
139 void
140 PackageManager::CollectPackageActions(PackageInfoRef package,
141 		Collector<PackageActionRef>& actionList)
142 {
143 	if (package->IsSystemPackage() || package->IsSystemDependency())
144 		return;
145 
146 	switch (package->State()) {
147 		case ACTIVATED:
148 		case INSTALLED:
149 			_CollectPackageActionsForActivatedOrInstalled(package, actionList);
150 			break;
151 		case NONE:
152 		case UNINSTALLED:
153 			actionList.Add(_CreateInstallPackageAction(package));
154 			break;
155 		case DOWNLOADING:
156 			HDINFO("no package actions for [%s] (downloading)",
157 				package->Name().String());
158 			break;
159 		case PENDING:
160 			HDINFO("no package actions for [%s] (pending)",
161 				package->Name().String());
162 			break;
163 		default:
164 			HDFATAL("unexpected status for package [%s]",
165 				package->Name().String());
166 			break;
167 	}
168 }
169 
170 
171 void
172 PackageManager::_CollectPackageActionsForActivatedOrInstalled(
173 		PackageInfoRef package,
174 		Collector<PackageActionRef>& actionList)
175 {
176 	actionList.Add(_CreateUninstallPackageAction(package));
177 
178 	// Add OpenPackageActions for each deskbar link found in the
179 	// package
180 	std::vector<DeskbarLink> foundLinks;
181 	if (OpenPackageProcess::FindAppToLaunch(package, foundLinks)
182 		&& foundLinks.size() < 4) {
183 		std::vector<DeskbarLink>::const_iterator it;
184 		for (it = foundLinks.begin(); it != foundLinks.end(); it++) {
185 			const DeskbarLink& aLink = *it;
186 			actionList.Add(_CreateOpenPackageAction(package, aLink));
187 		}
188 	}
189 }
190 
191 
192 PackageActionRef
193 PackageManager::_CreateUninstallPackageAction(const PackageInfoRef& package)
194 {
195 	BString title = B_TRANSLATE("Uninstall %PackageTitle%");
196 	title.ReplaceAll("%PackageTitle%", package->Title());
197 
198 	BMessage message(MSG_PKG_UNINSTALL);
199 	message.AddString(KEY_TITLE, title);
200 	message.AddString(KEY_PACKAGE_NAME, package->Name());
201 
202 	return PackageActionRef(new PackageAction(title, message));
203 }
204 
205 
206 PackageActionRef
207 PackageManager::_CreateInstallPackageAction(const PackageInfoRef& package)
208 {
209 	BString title = B_TRANSLATE("Install %PackageTitle%");
210 	title.ReplaceAll("%PackageTitle%", package->Title());
211 
212 	BMessage message(MSG_PKG_INSTALL);
213 	message.AddString(KEY_TITLE, title);
214 	message.AddString(KEY_PACKAGE_NAME, package->Name());
215 
216 	return PackageActionRef(new PackageAction(title, message));
217 }
218 
219 
220 PackageActionRef
221 PackageManager::_CreateOpenPackageAction(const PackageInfoRef& package,
222 	const DeskbarLink& link)
223 {
224 	BPath linkPath(link.Link());
225 	BString title = B_TRANSLATE("Open %DeskbarLink%");
226 	title.ReplaceAll("%DeskbarLink%", linkPath.Leaf());
227 
228 	BMessage deskbarLinkMessage;
229 	if (link.Archive(&deskbarLinkMessage) != B_OK)
230 		HDFATAL("unable to archive the deskbar link");
231 
232 	BMessage message(MSG_PKG_OPEN);
233 	message.AddString(KEY_TITLE, title);
234 	message.AddMessage(KEY_DESKBAR_LINK, &deskbarLinkMessage);
235 	message.AddString(KEY_PACKAGE_NAME, package->Name());
236 
237 	return PackageActionRef(new PackageAction(title, message));
238 }
239 
240 
241 void
242 PackageManager::SetCurrentActionPackage(PackageInfoRef package, bool install)
243 {
244 	BSolverPackage* solverPackage = _GetSolverPackage(package);
245 	fCurrentInstallPackage = install ? solverPackage : NULL;
246 	fCurrentUninstallPackage = install ? NULL : solverPackage;
247 }
248 
249 
250 status_t
251 PackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
252 {
253 	status_t result;
254 	try {
255 		result = BPackageManager::RefreshRepository(repoConfig);
256 	} catch (BFatalErrorException& ex) {
257 		HDERROR("Fatal error occurred while refreshing repository: "
258 			"%s (%s)", ex.Message().String(), ex.Details().String());
259 		result = ex.Error();
260 	} catch (BException& ex) {
261 		HDERROR("Exception occurred while refreshing "
262 			"repository: %s\n", ex.Message().String());
263 		result = B_ERROR;
264 	}
265 
266 	return result;
267 }
268 
269 
270 status_t
271 PackageManager::DownloadPackage(const BString& fileURL,
272 	const BEntry& targetEntry, const BString& checksum)
273 {
274 	status_t result;
275 	try {
276 		result = BPackageManager::DownloadPackage(fileURL, targetEntry,
277 			checksum);
278 	} catch (BFatalErrorException& ex) {
279 		HDERROR("Fatal error occurred while downloading package: "
280 			"%s: %s (%s)", fileURL.String(), ex.Message().String(),
281 			ex.Details().String());
282 		result = ex.Error();
283 	} catch (BException& ex) {
284 		HDERROR("Exception occurred while downloading package "
285 			"%s: %s", fileURL.String(), ex.Message().String());
286 		result = B_ERROR;
287 	}
288 
289 	return result;
290 }
291 
292 
293 void
294 PackageManager::AddProgressListener(PackageProgressListener* listener)
295 {
296 	fPackageProgressListeners.AddItem(listener);
297 }
298 
299 
300 void
301 PackageManager::RemoveProgressListener(PackageProgressListener* listener)
302 {
303 	fPackageProgressListeners.RemoveItem(listener);
304 }
305 
306 
307 void
308 PackageManager::HandleProblems()
309 {
310 	if (fProblemWindow == NULL)
311 		fProblemWindow = new ProblemWindow;
312 
313 	ProblemWindow::SolverPackageSet installPackages;
314 	ProblemWindow::SolverPackageSet uninstallPackages;
315 	if (fCurrentInstallPackage != NULL)
316 		installPackages.insert(fCurrentInstallPackage);
317 
318 	if (fCurrentUninstallPackage != NULL)
319 		uninstallPackages.insert(fCurrentUninstallPackage);
320 
321 	if (!fProblemWindow->Go(fSolver,installPackages, uninstallPackages))
322 		throw BAbortedByUserException();
323 }
324 
325 
326 void
327 PackageManager::ConfirmChanges(bool fromMostSpecific)
328 {
329 	ResultWindow* window = new ResultWindow;
330 	ObjectDeleter<ResultWindow> windowDeleter(window);
331 
332 	bool hasOtherChanges = false;
333 	int32 count = fInstalledRepositories.CountItems();
334 
335 	if (fromMostSpecific) {
336 		for (int32 i = count - 1; i >= 0; i--)
337 			hasOtherChanges
338 				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
339 	} else {
340 		for (int32 i = 0; i < count; i++)
341 			hasOtherChanges
342 				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
343 	}
344 
345 	if (!hasOtherChanges) {
346 		_NotifyChangesConfirmed();
347 		return;
348 	}
349 
350 	// show the window
351 	if (windowDeleter.Detach()->Go() == 0)
352 		throw BAbortedByUserException();
353 
354 	_NotifyChangesConfirmed();
355 }
356 
357 
358 void
359 PackageManager::Warn(status_t error, const char* format, ...)
360 {
361 	// TODO: Show alert to user
362 
363 	va_list args;
364 	va_start(args, format);
365 	vfprintf(stderr, format, args);
366 	va_end(args);
367 
368 	if (error == B_OK)
369 		printf("\n");
370 	else
371 		printf(": %s\n", strerror(error));
372 }
373 
374 
375 void
376 PackageManager::ProgressPackageDownloadStarted(const char* packageName)
377 {
378 	ProgressPackageDownloadActive(packageName, 0.0f, 0, 0);
379 }
380 
381 
382 void
383 PackageManager::ProgressPackageDownloadActive(const char* packageName,
384 	float completionPercentage, off_t bytes, off_t totalBytes)
385 {
386 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
387 		fPackageProgressListeners.ItemAt(i)->DownloadProgressChanged(
388 			packageName, completionPercentage);
389 	}
390 }
391 
392 
393 void
394 PackageManager::ProgressPackageDownloadComplete(const char* packageName)
395 {
396 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
397 		fPackageProgressListeners.ItemAt(i)->DownloadProgressComplete(
398 			packageName);
399 	}
400 }
401 
402 
403 void
404 PackageManager::ProgressPackageChecksumStarted(const char* title)
405 {
406 	// TODO: implement
407 }
408 
409 
410 void
411 PackageManager::ProgressPackageChecksumComplete(const char* title)
412 {
413 	// TODO: implement
414 }
415 
416 
417 void
418 PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
419 {
420 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
421 		fPackageProgressListeners.ItemAt(i)->StartApplyingChanges(repository);
422 }
423 
424 
425 void
426 PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
427 	const BCommitTransactionResult& result)
428 {
429 	// TODO: implement
430 }
431 
432 
433 void
434 PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
435 {
436 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
437 		fPackageProgressListeners.ItemAt(i)->ApplyingChangesDone(repository);
438 
439 	if (BPackageRoster().IsRebootNeeded()) {
440 		BString infoString(B_TRANSLATE("A reboot is necessary to complete the "
441 			"installation process."));
442 		BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Reboot required"),
443 			infoString, B_TRANSLATE("Close"), NULL, NULL,
444 			B_WIDTH_AS_USUAL, B_INFO_ALERT);
445 		if (alert != NULL)
446 			alert->Go();
447 	}
448 }
449 
450 
451 bool
452 PackageManager::_AddResults(InstalledRepository& repository,
453 	ResultWindow* window)
454 {
455 	if (!repository.HasChanges())
456 		return false;
457 
458 	ProblemWindow::SolverPackageSet installPackages;
459 	ProblemWindow::SolverPackageSet uninstallPackages;
460 	if (fCurrentInstallPackage != NULL)
461 		installPackages.insert(fCurrentInstallPackage);
462 
463 	if (fCurrentUninstallPackage != NULL)
464 		uninstallPackages.insert(fCurrentUninstallPackage);
465 
466 	return window->AddLocationChanges(repository.Name(),
467 		repository.PackagesToActivate(), installPackages,
468 		repository.PackagesToDeactivate(), uninstallPackages);
469 }
470 
471 
472 void
473 PackageManager::_NotifyChangesConfirmed()
474 {
475 	int32 count = fInstalledRepositories.CountItems();
476 	for (int32 i = 0; i < count; i++) {
477 		for (int32 j = 0; j < fPackageProgressListeners.CountItems(); j++) {
478 			fPackageProgressListeners.ItemAt(j)->ConfirmedChanges(
479 				*fInstalledRepositories.ItemAt(i));
480 		}
481 	}
482 }
483 
484 
485 BSolverPackage*
486 PackageManager::_GetSolverPackage(PackageInfoRef package)
487 {
488 	int32 flags = BSolver::B_FIND_IN_NAME;
489 	if (package->State() == ACTIVATED || package->State() == INSTALLED)
490 		flags |= BSolver::B_FIND_INSTALLED_ONLY;
491 
492 	BObjectList<BSolverPackage> packages;
493 	status_t result = Solver()->FindPackages(package->Name(), flags, packages);
494 	if (result == B_OK) {
495 		for (int32 i = 0; i < packages.CountItems(); i++) {
496 			BSolverPackage* solverPackage = packages.ItemAt(i);
497 			if (solverPackage->Name() != package->Name())
498 				continue;
499 			else if (package->State() == NONE
500 				&& dynamic_cast<BPackageManager::RemoteRepository*>(
501 					solverPackage->Repository()) == NULL) {
502 				continue;
503 			}
504 			return solverPackage;
505 		}
506 	}
507 
508 	return NULL;
509 }
510