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