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