xref: /haiku/src/apps/haikudepot/packagemanagement/PackageManager.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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) && 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, const DeskbarLink& link)
222 {
223 	BString title = B_TRANSLATE("Open %DeskbarLink%");
224 	title.ReplaceAll("%DeskbarLink%", link.Title());
225 
226 	BMessage deskbarLinkMessage;
227 	if (link.Archive(&deskbarLinkMessage) != B_OK)
228 		HDFATAL("unable to archive the deskbar link");
229 
230 	BMessage message(MSG_PKG_OPEN);
231 	message.AddString(KEY_TITLE, title);
232 	message.AddMessage(KEY_DESKBAR_LINK, &deskbarLinkMessage);
233 	message.AddString(KEY_PACKAGE_NAME, package->Name());
234 
235 	return PackageActionRef(new PackageAction(title, message));
236 }
237 
238 
239 void
240 PackageManager::SetCurrentActionPackage(PackageInfoRef package, bool install)
241 {
242 	BSolverPackage* solverPackage = _GetSolverPackage(package);
243 	fCurrentInstallPackage = install ? solverPackage : NULL;
244 	fCurrentUninstallPackage = install ? NULL : solverPackage;
245 }
246 
247 
248 status_t
249 PackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
250 {
251 	status_t result;
252 	try {
253 		result = BPackageManager::RefreshRepository(repoConfig);
254 	} catch (BFatalErrorException& ex) {
255 		HDERROR("Fatal error occurred while refreshing repository: "
256 			"%s (%s)", ex.Message().String(), ex.Details().String());
257 		result = ex.Error();
258 	} catch (BException& ex) {
259 		HDERROR("Exception occurred while refreshing "
260 			"repository: %s\n", ex.Message().String());
261 		result = B_ERROR;
262 	}
263 
264 	return result;
265 }
266 
267 
268 status_t
269 PackageManager::DownloadPackage(const BString& fileURL,
270 	const BEntry& targetEntry, const BString& checksum)
271 {
272 	status_t result;
273 	try {
274 		result = BPackageManager::DownloadPackage(fileURL, targetEntry,
275 			checksum);
276 	} catch (BFatalErrorException& ex) {
277 		HDERROR("Fatal error occurred while downloading package: "
278 			"%s: %s (%s)", fileURL.String(), ex.Message().String(),
279 			ex.Details().String());
280 		result = ex.Error();
281 	} catch (BException& ex) {
282 		HDERROR("Exception occurred while downloading package "
283 			"%s: %s", fileURL.String(), ex.Message().String());
284 		result = B_ERROR;
285 	}
286 
287 	return result;
288 }
289 
290 
291 void
292 PackageManager::AddProgressListener(PackageProgressListener* listener)
293 {
294 	fPackageProgressListeners.AddItem(listener);
295 }
296 
297 
298 void
299 PackageManager::RemoveProgressListener(PackageProgressListener* listener)
300 {
301 	fPackageProgressListeners.RemoveItem(listener);
302 }
303 
304 
305 void
306 PackageManager::HandleProblems()
307 {
308 	if (fProblemWindow == NULL)
309 		fProblemWindow = new ProblemWindow;
310 
311 	ProblemWindow::SolverPackageSet installPackages;
312 	ProblemWindow::SolverPackageSet uninstallPackages;
313 	if (fCurrentInstallPackage != NULL)
314 		installPackages.insert(fCurrentInstallPackage);
315 
316 	if (fCurrentUninstallPackage != NULL)
317 		uninstallPackages.insert(fCurrentUninstallPackage);
318 
319 	if (!fProblemWindow->Go(fSolver,installPackages, uninstallPackages))
320 		throw BAbortedByUserException();
321 }
322 
323 
324 void
325 PackageManager::ConfirmChanges(bool fromMostSpecific)
326 {
327 	ResultWindow* window = new ResultWindow;
328 	ObjectDeleter<ResultWindow> windowDeleter(window);
329 
330 	bool hasOtherChanges = false;
331 	int32 count = fInstalledRepositories.CountItems();
332 
333 	if (fromMostSpecific) {
334 		for (int32 i = count - 1; i >= 0; i--)
335 			hasOtherChanges
336 				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
337 	} else {
338 		for (int32 i = 0; i < count; i++)
339 			hasOtherChanges
340 				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
341 	}
342 
343 	if (!hasOtherChanges) {
344 		_NotifyChangesConfirmed();
345 		return;
346 	}
347 
348 	// show the window
349 	if (windowDeleter.Detach()->Go() == 0)
350 		throw BAbortedByUserException();
351 
352 	_NotifyChangesConfirmed();
353 }
354 
355 
356 void
357 PackageManager::Warn(status_t error, const char* format, ...)
358 {
359 	// TODO: Show alert to user
360 
361 	va_list args;
362 	va_start(args, format);
363 	vfprintf(stderr, format, args);
364 	va_end(args);
365 
366 	if (error == B_OK)
367 		printf("\n");
368 	else
369 		printf(": %s\n", strerror(error));
370 }
371 
372 
373 void
374 PackageManager::ProgressPackageDownloadStarted(const char* packageName)
375 {
376 	ProgressPackageDownloadActive(packageName, 0.0f, 0, 0);
377 }
378 
379 
380 void
381 PackageManager::ProgressPackageDownloadActive(const char* packageName,
382 	float completionPercentage, off_t bytes, off_t totalBytes)
383 {
384 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
385 		fPackageProgressListeners.ItemAt(i)->DownloadProgressChanged(
386 			packageName, completionPercentage);
387 	}
388 }
389 
390 
391 void
392 PackageManager::ProgressPackageDownloadComplete(const char* packageName)
393 {
394 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
395 		fPackageProgressListeners.ItemAt(i)->DownloadProgressComplete(
396 			packageName);
397 	}
398 }
399 
400 
401 void
402 PackageManager::ProgressPackageChecksumStarted(const char* title)
403 {
404 	// TODO: implement
405 }
406 
407 
408 void
409 PackageManager::ProgressPackageChecksumComplete(const char* title)
410 {
411 	// TODO: implement
412 }
413 
414 
415 void
416 PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
417 {
418 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
419 		fPackageProgressListeners.ItemAt(i)->StartApplyingChanges(repository);
420 }
421 
422 
423 void
424 PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
425 	const BCommitTransactionResult& result)
426 {
427 	// TODO: implement
428 }
429 
430 
431 void
432 PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
433 {
434 	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
435 		fPackageProgressListeners.ItemAt(i)->ApplyingChangesDone(repository);
436 
437 	if (BPackageRoster().IsRebootNeeded()) {
438 		BString infoString(B_TRANSLATE("A reboot is necessary to complete the "
439 			"installation process."));
440 		BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Reboot required"),
441 			infoString, B_TRANSLATE("Close"), NULL, NULL,
442 			B_WIDTH_AS_USUAL, B_INFO_ALERT);
443 		if (alert != NULL)
444 			alert->Go();
445 	}
446 }
447 
448 
449 bool
450 PackageManager::_AddResults(InstalledRepository& repository,
451 	ResultWindow* window)
452 {
453 	if (!repository.HasChanges())
454 		return false;
455 
456 	ProblemWindow::SolverPackageSet installPackages;
457 	ProblemWindow::SolverPackageSet uninstallPackages;
458 	if (fCurrentInstallPackage != NULL)
459 		installPackages.insert(fCurrentInstallPackage);
460 
461 	if (fCurrentUninstallPackage != NULL)
462 		uninstallPackages.insert(fCurrentUninstallPackage);
463 
464 	return window->AddLocationChanges(repository.Name(),
465 		repository.PackagesToActivate(), installPackages,
466 		repository.PackagesToDeactivate(), uninstallPackages);
467 }
468 
469 
470 void
471 PackageManager::_NotifyChangesConfirmed()
472 {
473 	int32 count = fInstalledRepositories.CountItems();
474 	for (int32 i = 0; i < count; i++) {
475 		for (int32 j = 0; j < fPackageProgressListeners.CountItems(); j++) {
476 			fPackageProgressListeners.ItemAt(j)->ConfirmedChanges(
477 				*fInstalledRepositories.ItemAt(i));
478 		}
479 	}
480 }
481 
482 
483 BSolverPackage*
484 PackageManager::_GetSolverPackage(PackageInfoRef package)
485 {
486 	int32 flags = BSolver::B_FIND_IN_NAME;
487 	if (package->State() == ACTIVATED || package->State() == INSTALLED)
488 		flags |= BSolver::B_FIND_INSTALLED_ONLY;
489 
490 	BObjectList<BSolverPackage> packages;
491 	status_t result = Solver()->FindPackages(package->Name(), flags, packages);
492 	if (result == B_OK) {
493 		for (int32 i = 0; i < packages.CountItems(); i++) {
494 			BSolverPackage* solverPackage = packages.ItemAt(i);
495 			if (solverPackage->Name() != package->Name())
496 				continue;
497 			else if (package->State() == NONE
498 				&& dynamic_cast<BPackageManager::RemoteRepository*>(
499 					solverPackage->Repository()) == NULL) {
500 				continue;
501 			}
502 			return solverPackage;
503 		}
504 	}
505 
506 	return NULL;
507 }
508