xref: /haiku/src/bin/pkgman/PackageManager.cpp (revision 8a6724a0ee3803f1e9f487d8111bb3f6cb8d16db)
1 /*
2  * Copyright 2013-2015, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler <axeld@pinc-software.de>
7  *		Rene Gollent <rene@gollent.com>
8  *		Ingo Weinhold <ingo_weinhold@gmx.de>
9  */
10 
11 
12 #include "PackageManager.h"
13 
14 #include <sys/ioctl.h>
15 #include <unistd.h>
16 
17 #include <package/CommitTransactionResult.h>
18 #include <package/DownloadFileRequest.h>
19 #include <package/RefreshRepositoryRequest.h>
20 #include <package/solver/SolverPackage.h>
21 #include <package/solver/SolverProblem.h>
22 #include <package/solver/SolverProblemSolution.h>
23 
24 #include "pkgman.h"
25 
26 
27 using namespace BPackageKit::BPrivate;
28 
29 
30 PackageManager::PackageManager(BPackageInstallationLocation location,
31 	bool interactive)
32 	:
33 	BPackageManager(location, &fClientInstallationInterface, this),
34 	BPackageManager::UserInteractionHandler(),
35 	fDecisionProvider(interactive),
36 	fClientInstallationInterface(),
37 	fInteractive(interactive)
38 {
39 }
40 
41 
42 PackageManager::~PackageManager()
43 {
44 }
45 
46 
47 void
48 PackageManager::SetInteractive(bool interactive)
49 {
50 	fInteractive = interactive;
51 	fDecisionProvider.SetInteractive(interactive);
52 }
53 
54 
55 void
56 PackageManager::JobFailed(BSupportKit::BJob* job)
57 {
58 	BString error = job->ErrorString();
59 	if (error.Length() > 0) {
60 		error.ReplaceAll("\n", "\n*** ");
61 		fprintf(stderr, "%s", error.String());
62 	}
63 }
64 
65 
66 void
67 PackageManager::JobAborted(BSupportKit::BJob* job)
68 {
69 	DIE(job->Result(), "aborted");
70 }
71 
72 
73 void
74 PackageManager::HandleProblems()
75 {
76 	printf("Encountered problems:\n");
77 
78 	int32 problemCount = fSolver->CountProblems();
79 	for (int32 i = 0; i < problemCount; i++) {
80 		// print problem and possible solutions
81 		BSolverProblem* problem = fSolver->ProblemAt(i);
82 		printf("problem %" B_PRId32 ": %s\n", i + 1,
83 			problem->ToString().String());
84 
85 		int32 solutionCount = problem->CountSolutions();
86 		for (int32 k = 0; k < solutionCount; k++) {
87 			const BSolverProblemSolution* solution = problem->SolutionAt(k);
88 			printf("  solution %" B_PRId32 ":\n", k + 1);
89 			int32 elementCount = solution->CountElements();
90 			for (int32 l = 0; l < elementCount; l++) {
91 				const BSolverProblemSolutionElement* element
92 					= solution->ElementAt(l);
93 				printf("    - %s\n", element->ToString().String());
94 			}
95 		}
96 
97 		if (!fInteractive)
98 			continue;
99 
100 		// let the user choose a solution
101 		printf("Please select a solution, skip the problem for now or quit.\n");
102 		for (;;) {
103 			if (solutionCount > 1)
104 				printf("select [1...%" B_PRId32 "/s/q]: ", solutionCount);
105 			else
106 				printf("select [1/s/q]: ");
107 
108 			char buffer[32];
109 			if (fgets(buffer, sizeof(buffer), stdin) == NULL
110 				|| strcmp(buffer, "q\n") == 0) {
111 				exit(1);
112 			}
113 
114 			if (strcmp(buffer, "s\n") == 0)
115 				break;
116 
117 			char* end;
118 			long selected = strtol(buffer, &end, 0);
119 			if (end == buffer || *end != '\n' || selected < 1
120 				|| selected > solutionCount) {
121 				printf("*** invalid input\n");
122 				continue;
123 			}
124 
125 			status_t error = fSolver->SelectProblemSolution(problem,
126 				problem->SolutionAt(selected - 1));
127 			if (error != B_OK)
128 				DIE(error, "failed to set solution");
129 			break;
130 		}
131 	}
132 
133 	if (problemCount > 0 && !fInteractive)
134 		exit(1);
135 }
136 
137 
138 void
139 PackageManager::ConfirmChanges(bool fromMostSpecific)
140 {
141 	printf("The following changes will be made:\n");
142 
143 	int32 count = fInstalledRepositories.CountItems();
144 	if (fromMostSpecific) {
145 		for (int32 i = count - 1; i >= 0; i--)
146 			_PrintResult(*fInstalledRepositories.ItemAt(i));
147 	} else {
148 		for (int32 i = 0; i < count; i++)
149 			_PrintResult(*fInstalledRepositories.ItemAt(i));
150 	}
151 
152 	if (!fDecisionProvider.YesNoDecisionNeeded(BString(), "Continue?", "yes",
153 			"no", "yes")) {
154 		exit(1);
155 	}
156 }
157 
158 
159 void
160 PackageManager::Warn(status_t error, const char* format, ...)
161 {
162 	va_list args;
163 	va_start(args, format);
164 	vfprintf(stderr, format, args);
165 	va_end(args);
166 
167 	if (error == B_OK)
168 		printf("\n");
169 	else
170 		printf(": %s\n", strerror(error));
171 }
172 
173 
174 void
175 PackageManager::ProgressPackageDownloadStarted(const char* packageName)
176 {
177 	printf("Downloading %s...\n", packageName);
178 }
179 
180 
181 void
182 PackageManager::ProgressPackageDownloadActive(const char* packageName,
183 	float completionPercentage, off_t bytes, off_t totalBytes)
184 {
185 	if (!fInteractive)
186 		return;
187 
188 	static const char* progressChars[] = {
189 		"\xE2\x96\x8F",
190 		"\xE2\x96\x8E",
191 		"\xE2\x96\x8D",
192 		"\xE2\x96\x8C",
193 		"\xE2\x96\x8B",
194 		"\xE2\x96\x8A",
195 		"\xE2\x96\x89",
196 		"\xE2\x96\x88",
197 	};
198 
199 	int width = 70;
200 
201 	struct winsize winSize;
202 	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winSize) == 0
203 		&& winSize.ws_col < 77) {
204 		// We need 7 characters for the percent display
205 		width = winSize.ws_col - 7;
206 	}
207 
208 	int position;
209 	int ipart = (int)(completionPercentage * width);
210 	int fpart = (int)(((completionPercentage * width) - ipart) * 8);
211 
212 	printf("\r"); // return to the beginning of the line
213 
214 	for (position = 0; position < width; position++) {
215 		if (position < ipart) {
216 			// This part is fully downloaded, show a full block
217 			printf(progressChars[7]);
218 		} else if (position > ipart) {
219 			// This part is not downloaded, show a space
220 			printf(" ");
221 		} else {
222 			// This part is partially downloaded
223 			printf(progressChars[fpart]);
224 		}
225 	}
226 
227 	// Also print the progress percentage
228 	printf(" %3d%%", (int)(completionPercentage * 100));
229 
230 	fflush(stdout);
231 }
232 
233 
234 void
235 PackageManager::ProgressPackageDownloadComplete(const char* packageName)
236 {
237 	if (fInteractive) {
238 		// Overwrite the progress bar with whitespace
239 		printf("\r");
240 		struct winsize w;
241 		ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
242 		for (int i = 0; i < (w.ws_col); i++)
243 			printf(" ");
244 		printf("\r\x1b[1A"); // Go to previous line.
245 	}
246 
247 	printf("Downloading %s...done.\n", packageName);
248 }
249 
250 
251 void
252 PackageManager::ProgressPackageChecksumStarted(const char* title)
253 {
254 	printf("%s...", title);
255 }
256 
257 
258 void
259 PackageManager::ProgressPackageChecksumComplete(const char* title)
260 {
261 	printf("done.\n");
262 }
263 
264 
265 void
266 PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
267 {
268 	printf("[%s] Applying changes ...\n", repository.Name().String());
269 }
270 
271 
272 void
273 PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
274 	const BCommitTransactionResult& result)
275 {
276 	const char* repositoryName = repository.Name().String();
277 
278 	int32 issueCount = result.CountIssues();
279 	for (int32 i = 0; i < issueCount; i++) {
280 		const BTransactionIssue* issue = result.IssueAt(i);
281 		if (issue->PackageName().IsEmpty()) {
282 			printf("[%s] warning: %s\n", repositoryName,
283 				issue->ToString().String());
284 		} else {
285 			printf("[%s] warning: package %s: %s\n", repositoryName,
286 				issue->PackageName().String(), issue->ToString().String());
287 		}
288 	}
289 
290 	printf("[%s] Changes applied. Old activation state backed up in \"%s\"\n",
291 		repositoryName, result.OldStateDirectory().String());
292 	printf("[%s] Cleaning up ...\n", repositoryName);
293 }
294 
295 
296 void
297 PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
298 {
299 	printf("[%s] Done.\n", repository.Name().String());
300 }
301 
302 
303 void
304 PackageManager::_PrintResult(InstalledRepository& installationRepository)
305 {
306 	if (!installationRepository.HasChanges())
307 		return;
308 
309 	printf("  in %s:\n", installationRepository.Name().String());
310 
311 	PackageList& packagesToActivate
312 		= installationRepository.PackagesToActivate();
313 	PackageList& packagesToDeactivate
314 		= installationRepository.PackagesToDeactivate();
315 
316 	BStringList upgradedPackages;
317 	BStringList upgradedPackageVersions;
318 	for (int32 i = 0;
319 		BSolverPackage* installPackage = packagesToActivate.ItemAt(i);
320 		i++) {
321 		for (int32 j = 0;
322 			BSolverPackage* uninstallPackage = packagesToDeactivate.ItemAt(j);
323 			j++) {
324 			if (installPackage->Info().Name() == uninstallPackage->Info().Name()) {
325 				upgradedPackages.Add(installPackage->Info().Name());
326 				upgradedPackageVersions.Add(uninstallPackage->Info().Version().ToString());
327 				break;
328 			}
329 		}
330 	}
331 
332 	for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i);
333 		i++) {
334 		BString repository;
335 		if (dynamic_cast<MiscLocalRepository*>(package->Repository()) != NULL)
336 			repository = "local file";
337 		else
338 			repository.SetToFormat("repository %s", package->Repository()->Name().String());
339 
340 		int position = upgradedPackages.IndexOf(package->Info().Name());
341 		if (position >= 0) {
342 			printf("    upgrade package %s-%s to %s from %s\n",
343 				package->Info().Name().String(),
344 				upgradedPackageVersions.StringAt(position).String(),
345 				package->Info().Version().ToString().String(),
346 				repository.String());
347 		} else {
348 			printf("    install package %s-%s from %s\n",
349 				package->Info().Name().String(),
350 				package->Info().Version().ToString().String(),
351 				repository.String());
352 		}
353 	}
354 
355 	for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i);
356 		i++) {
357 		if (upgradedPackages.HasString(package->Info().Name()))
358 			continue;
359 		printf("    uninstall package %s\n", package->VersionedName().String());
360 	}
361 // TODO: Print file/download sizes. Unfortunately our package infos don't
362 // contain the file size. Which is probably correct. The file size (and possibly
363 // other information) should, however, be provided by the repository cache in
364 // some way. Extend BPackageInfo? Create a BPackageFileInfo?
365 }
366