xref: /haiku/src/apps/haikudepot/packagemanagement/PackageManager.cpp (revision 592a3b6921f10ad84e44ad74e066c23466ab85cb)
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->IsSystemPackage() || package->IsSystemDependency())
146 		return;
147 
148 	switch (package->State()) {
149 		case ACTIVATED:
150 		case INSTALLED:
151 			_CollectPackageActionsForActivatedOrInstalled(package, actionList);
152 			break;
153 		case NONE:
154 		case UNINSTALLED:
155 			actionList.Add(_CreateInstallPackageAction(package));
156 			break;
157 		case DOWNLOADING:
158 			HDINFO("no package actions for [%s] (downloading)",
159 				package->Name().String());
160 			break;
161 		case PENDING:
162 			HDINFO("no package actions for [%s] (pending)",
163 				package->Name().String());
164 			break;
165 		default:
166 			HDFATAL("unexpected status for package [%s]",
167 				package->Name().String());
168 			break;
169 	}
170 }
171 
172 
173 void
174 PackageManager::_CollectPackageActionsForActivatedOrInstalled(
175 		PackageInfoRef package,
176 		Collector<PackageActionRef>& actionList)
177 {
178 	actionList.Add(_CreateUninstallPackageAction(package));
179 
180 	// Add OpenPackageActions for each deskbar link found in the
181 	// package
182 	std::vector<DeskbarLink> foundLinks;
183 	if (OpenPackageProcess::FindAppToLaunch(package, foundLinks) && foundLinks.size() < 4) {
184 		std::vector<DeskbarLink>::const_iterator it;
185 		for (it = foundLinks.begin(); it != foundLinks.end(); it++) {
186 			const DeskbarLink& aLink = *it;
187 			actionList.Add(_CreateOpenPackageAction(package, aLink));
188 		}
189 	}
190 }
191 
192 
193 PackageActionRef
194 PackageManager::_CreateUninstallPackageAction(const PackageInfoRef& package)
195 {
196 	BString actionTitle = B_TRANSLATE("Uninstall %PackageTitle%");
197 	BString packageTitle;
198 	PackageUtils::TitleOrName(package, packageTitle);
199 	actionTitle.ReplaceAll("%PackageTitle%", packageTitle);
200 
201 	BMessage message(MSG_PKG_UNINSTALL);
202 	message.AddString(KEY_TITLE, actionTitle);
203 	message.AddString(KEY_PACKAGE_NAME, package->Name());
204 
205 	return PackageActionRef(new PackageAction(actionTitle, message), true);
206 }
207 
208 
209 PackageActionRef
210 PackageManager::_CreateInstallPackageAction(const PackageInfoRef& package)
211 {
212 	BString actionTitle = B_TRANSLATE("Install %PackageTitle%");
213 	BString packageTitle;
214 	PackageUtils::TitleOrName(package, packageTitle);
215 	actionTitle.ReplaceAll("%PackageTitle%", packageTitle);
216 
217 	BMessage message(MSG_PKG_INSTALL);
218 	message.AddString(KEY_TITLE, actionTitle);
219 	message.AddString(KEY_PACKAGE_NAME, package->Name());
220 
221 	return PackageActionRef(new PackageAction(actionTitle, message), true);
222 }
223 
224 
225 PackageActionRef
226 PackageManager::_CreateOpenPackageAction(const PackageInfoRef& package, const DeskbarLink& link)
227 {
228 	BString title = B_TRANSLATE("Open %DeskbarLink%");
229 	title.ReplaceAll("%DeskbarLink%", link.Title());
230 
231 	BMessage deskbarLinkMessage;
232 	if (link.Archive(&deskbarLinkMessage) != B_OK)
233 		HDFATAL("unable to archive the deskbar link");
234 
235 	BMessage message(MSG_PKG_OPEN);
236 	message.AddString(KEY_TITLE, title);
237 	message.AddMessage(KEY_DESKBAR_LINK, &deskbarLinkMessage);
238 	message.AddString(KEY_PACKAGE_NAME, package->Name());
239 
240 	return PackageActionRef(new PackageAction(title, message), true);
241 }
242 
243 
244 void
245 PackageManager::SetCurrentActionPackage(PackageInfoRef package, bool install)
246 {
247 	BSolverPackage* solverPackage = _GetSolverPackage(package);
248 	fCurrentInstallPackage = install ? solverPackage : NULL;
249 	fCurrentUninstallPackage = install ? NULL : solverPackage;
250 }
251 
252 
253 status_t
254 PackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
255 {
256 	status_t result;
257 	try {
258 		result = BPackageManager::RefreshRepository(repoConfig);
259 	} catch (BFatalErrorException& ex) {
260 		HDERROR("Fatal error occurred while refreshing repository: "
261 			"%s (%s)", ex.Message().String(), ex.Details().String());
262 		result = ex.Error();
263 	} catch (BException& ex) {
264 		HDERROR("Exception occurred while refreshing "
265 			"repository: %s\n", ex.Message().String());
266 		result = B_ERROR;
267 	}
268 
269 	return result;
270 }
271 
272 
273 status_t
274 PackageManager::DownloadPackage(const BString& fileURL,
275 	const BEntry& targetEntry, const BString& checksum)
276 {
277 	status_t result;
278 	try {
279 		result = BPackageManager::DownloadPackage(fileURL, targetEntry,
280 			checksum);
281 	} catch (BFatalErrorException& ex) {
282 		HDERROR("Fatal error occurred while downloading package: "
283 			"%s: %s (%s)", fileURL.String(), ex.Message().String(),
284 			ex.Details().String());
285 		result = ex.Error();
286 	} catch (BException& ex) {
287 		HDERROR("Exception occurred while downloading package "
288 			"%s: %s", fileURL.String(), ex.Message().String());
289 		result = B_ERROR;
290 	}
291 
292 	return result;
293 }
294 
295 
296 void
297 PackageManager::AddProgressListener(PackageProgressListener* listener)
298 {
299 	fPackageProgressListeners.AddItem(listener);
300 }
301 
302 
303 void
304 PackageManager::RemoveProgressListener(PackageProgressListener* listener)
305 {
306 	fPackageProgressListeners.RemoveItem(listener);
307 }
308 
309 
310 void
311 PackageManager::HandleProblems()
312 {
313 	if (fProblemWindow == NULL)
314 		fProblemWindow = new ProblemWindow;
315 
316 	ProblemWindow::SolverPackageSet installPackages;
317 	ProblemWindow::SolverPackageSet uninstallPackages;
318 	if (fCurrentInstallPackage != NULL)
319 		installPackages.insert(fCurrentInstallPackage);
320 
321 	if (fCurrentUninstallPackage != NULL)
322 		uninstallPackages.insert(fCurrentUninstallPackage);
323 
324 	if (!fProblemWindow->Go(fSolver,installPackages, uninstallPackages))
325 		throw BAbortedByUserException();
326 }
327 
328 
329 void
330 PackageManager::ConfirmChanges(bool fromMostSpecific)
331 {
332 	ResultWindow* window = new ResultWindow;
333 	ObjectDeleter<ResultWindow> windowDeleter(window);
334 
335 	bool hasOtherChanges = false;
336 	int32 count = fInstalledRepositories.CountItems();
337 
338 	if (fromMostSpecific) {
339 		for (int32 i = count - 1; i >= 0; i--)
340 			hasOtherChanges
341 				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
342 	} else {
343 		for (int32 i = 0; i < count; i++)
344 			hasOtherChanges
345 				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
346 	}
347 
348 	if (!hasOtherChanges) {
349 		_NotifyChangesConfirmed();
350 		return;
351 	}
352 
353 	// show the window
354 	if (windowDeleter.Detach()->Go() == 0)
355 		throw BAbortedByUserException();
356 
357 	_NotifyChangesConfirmed();
358 }
359 
360 
361 void
362 PackageManager::Warn(status_t error, const char* format, ...)
363 {
364 	// TODO: Show alert to user
365 
366 	va_list args;
367 	va_start(args, format);
368 	vfprintf(stderr, format, args);
369 	va_end(args);
370 
371 	if (error == B_OK)
372 		printf("\n");
373 	else
374 		printf(": %s\n", strerror(error));
375 }
376 
377 
378 void
379 PackageManager::ProgressPackageDownloadStarted(const char* packageName)
380 {
381 	ProgressPackageDownloadActive(packageName, 0.0f, 0, 0);
382 }
383 
384 
385 void
386 PackageManager::ProgressPackageDownloadActive(const char* packageName,
387 	float completionPercentage, off_t bytes, off_t totalBytes)
388 {
389 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
390 		fPackageProgressListeners.ItemAt(i)->DownloadProgressChanged(
391 			packageName, completionPercentage);
392 	}
393 }
394 
395 
396 void
397 PackageManager::ProgressPackageDownloadComplete(const char* packageName)
398 {
399 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
400 		fPackageProgressListeners.ItemAt(i)->DownloadProgressComplete(
401 			packageName);
402 	}
403 }
404 
405 
406 void
407 PackageManager::ProgressPackageChecksumStarted(const char* title)
408 {
409 	// TODO: implement
410 }
411 
412 
413 void
414 PackageManager::ProgressPackageChecksumComplete(const char* title)
415 {
416 	// TODO: implement
417 }
418 
419 
420 void
421 PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
422 {
423 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
424 		fPackageProgressListeners.ItemAt(i)->StartApplyingChanges(repository);
425 }
426 
427 
428 void
429 PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
430 	const BCommitTransactionResult& result)
431 {
432 	// TODO: implement
433 }
434 
435 
436 void
437 PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
438 {
439 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
440 		fPackageProgressListeners.ItemAt(i)->ApplyingChangesDone(repository);
441 
442 	if (BPackageRoster().IsRebootNeeded()) {
443 		BString infoString(B_TRANSLATE("A reboot is necessary to complete the "
444 			"installation process."));
445 		BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Reboot required"),
446 			infoString, B_TRANSLATE("Close"), NULL, NULL,
447 			B_WIDTH_AS_USUAL, B_INFO_ALERT);
448 		if (alert != NULL)
449 			alert->Go();
450 	}
451 }
452 
453 
454 bool
455 PackageManager::_AddResults(InstalledRepository& repository,
456 	ResultWindow* window)
457 {
458 	if (!repository.HasChanges())
459 		return false;
460 
461 	ProblemWindow::SolverPackageSet installPackages;
462 	ProblemWindow::SolverPackageSet uninstallPackages;
463 	if (fCurrentInstallPackage != NULL)
464 		installPackages.insert(fCurrentInstallPackage);
465 
466 	if (fCurrentUninstallPackage != NULL)
467 		uninstallPackages.insert(fCurrentUninstallPackage);
468 
469 	return window->AddLocationChanges(repository.Name(),
470 		repository.PackagesToActivate(), installPackages,
471 		repository.PackagesToDeactivate(), uninstallPackages);
472 }
473 
474 
475 void
476 PackageManager::_NotifyChangesConfirmed()
477 {
478 	int32 count = fInstalledRepositories.CountItems();
479 	for (int32 i = 0; i < count; i++) {
480 		for (int32 j = 0; j < fPackageProgressListeners.CountItems(); j++) {
481 			fPackageProgressListeners.ItemAt(j)->ConfirmedChanges(
482 				*fInstalledRepositories.ItemAt(i));
483 		}
484 	}
485 }
486 
487 
488 BSolverPackage*
489 PackageManager::_GetSolverPackage(PackageInfoRef package)
490 {
491 	int32 flags = BSolver::B_FIND_IN_NAME;
492 	if (package->State() == ACTIVATED || package->State() == INSTALLED)
493 		flags |= BSolver::B_FIND_INSTALLED_ONLY;
494 
495 	BObjectList<BSolverPackage> packages;
496 	status_t result = Solver()->FindPackages(package->Name(), flags, packages);
497 	if (result == B_OK) {
498 		for (int32 i = 0; i < packages.CountItems(); i++) {
499 			BSolverPackage* solverPackage = packages.ItemAt(i);
500 			if (solverPackage->Name() != package->Name())
501 				continue;
502 			else if (package->State() == NONE
503 				&& dynamic_cast<BPackageManager::RemoteRepository*>(
504 					solverPackage->Repository()) == NULL) {
505 				continue;
506 			}
507 			return solverPackage;
508 		}
509 	}
510 
511 	return NULL;
512 }
513