xref: /haiku/src/bin/package_repo/command_update.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
1 /*
2  * Copyright 2011-2013, Oliver Tappe <zooey@hirschkaefer.de>
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <dirent.h>
8 #include <errno.h>
9 #include <getopt.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <map>
15 
16 #include <Entry.h>
17 #include <ObjectList.h>
18 #include <Path.h>
19 #include <String.h>
20 
21 #include <package/hpkg/HPKGDefs.h>
22 #include <package/hpkg/PackageInfoAttributeValue.h>
23 #include <package/hpkg/RepositoryContentHandler.h>
24 #include <package/hpkg/RepositoryReader.h>
25 #include <package/hpkg/RepositoryWriter.h>
26 #include <package/hpkg/StandardErrorOutput.h>
27 #include <package/PackageInfo.h>
28 #include <package/PackageInfoContentHandler.h>
29 #include <package/RepositoryInfo.h>
30 
31 #include "package_repo.h"
32 
33 
34 using BPackageKit::BHPKG::BRepositoryWriterListener;
35 using BPackageKit::BHPKG::BRepositoryWriter;
36 using namespace BPackageKit::BHPKG;
37 using namespace BPackageKit;
38 
39 
40 bool operator< (const BPackageInfo & left, const BPackageInfo & right)
41 {
42 	if (left.Name() != right.Name())
43 		return left.Name() < right.Name();
44 	return left.Version().Compare(right.Version()) < 0;
45 }
46 
47 
48 namespace
49 {
50 
51 
52 typedef std::map<BPackageInfo, bool> PackageInfos;
53 
54 
55 status_t
56 parsePackageListFile(const char* packageListFileName,
57 	BObjectList<BString>* packageFileNames)
58 {
59 	FILE* packageListFile = fopen(packageListFileName, "r");
60 	if (packageListFile == NULL) {
61 		printf("Error: Unable to open %s\n", packageListFileName);
62 		return B_ENTRY_NOT_FOUND;
63 	}
64 	char buffer[128];
65 	while (fgets(buffer, sizeof(buffer), packageListFile) != NULL) {
66 		BString* packageFileName = new(std::nothrow) BString(buffer);
67 		if (packageFileName == NULL) {
68 			printf("Error: Out of memory when reading from %s\n",
69 				packageListFileName);
70 			fclose(packageListFile);
71 			return B_NO_MEMORY;
72 		}
73 		packageFileName->Trim();
74 		packageFileNames->AddItem(packageFileName);
75 	}
76 	fclose(packageListFile);
77 	return B_OK;
78 }
79 
80 
81 struct PackageInfosCollector : BRepositoryContentHandler {
82 	PackageInfosCollector(PackageInfos& packageInfos,
83 		BHPKG::BErrorOutput* errorOutput)
84 		:
85 		fPackageInfos(packageInfos),
86 		fErrorOutput(errorOutput),
87 		fRepositoryInfo(),
88 		fPackageInfo(),
89 		fPackageInfoContentHandler(fPackageInfo, fErrorOutput)
90 	{
91 	}
92 
93 	virtual status_t HandlePackage(const char* packageName)
94 	{
95 		fPackageInfo.Clear();
96 		return B_OK;
97 	}
98 
99 	virtual status_t HandlePackageAttribute(
100 		const BPackageInfoAttributeValue& value)
101 	{
102 		return fPackageInfoContentHandler.HandlePackageAttribute(value);
103 	}
104 
105 	virtual status_t HandlePackageDone(const char* packageName)
106 	{
107 		if (fPackageInfo.InitCheck() != B_OK) {
108 			const BString& name = fPackageInfo.Name();
109 			printf("Error: package-info in repository for '%s' is incomplete\n",
110 				name.IsEmpty() ? "<unknown-package>" : name.String());
111 			return B_BAD_DATA;
112 		}
113 		fPackageInfos[fPackageInfo] = false;
114 		return B_OK;
115 	}
116 
117 	virtual status_t HandleRepositoryInfo(const BRepositoryInfo& repositoryInfo)
118 	{
119 		fRepositoryInfo = repositoryInfo;
120 		return B_OK;
121 	}
122 
123 	virtual void HandleErrorOccurred()
124 	{
125 	}
126 
127 	const BRepositoryInfo& RepositoryInfo() const
128 	{
129 		return fRepositoryInfo;
130 	}
131 
132 private:
133 	PackageInfos& fPackageInfos;
134 	BHPKG::BErrorOutput* fErrorOutput;
135 	BRepositoryInfo fRepositoryInfo;
136 	BPackageInfo fPackageInfo;
137 	BPackageInfoContentHandler fPackageInfoContentHandler;
138 };
139 
140 
141 class RepositoryWriterListener	: public BRepositoryWriterListener {
142 public:
143 	RepositoryWriterListener(bool verbose, bool quiet)
144 		: fVerbose(verbose), fQuiet(quiet)
145 	{
146 	}
147 
148 	virtual void PrintErrorVarArgs(const char* format, va_list args)
149 	{
150 		vfprintf(stderr, format, args);
151 	}
152 
153 	virtual void OnPackageAdded(const BPackageInfo& packageInfo)
154 	{
155 	}
156 
157 	virtual void OnRepositoryInfoSectionDone(uint32 uncompressedSize)
158 	{
159 		if (fQuiet || !fVerbose)
160 			return;
161 
162 		printf("----- Repository Info Section --------------------\n");
163 		printf("repository info size:    %10" B_PRIu32 " (uncompressed)\n",
164 			uncompressedSize);
165 	}
166 
167 	virtual void OnPackageAttributesSectionDone(uint32 stringCount,
168 		uint32 uncompressedSize)
169 	{
170 		if (fQuiet || !fVerbose)
171 			return;
172 
173 		printf("----- Package Attribute Section -------------------\n");
174 		printf("string count:            %10" B_PRIu32 "\n", stringCount);
175 		printf("package attributes size: %10" B_PRIu32 " (uncompressed)\n",
176 			uncompressedSize);
177 	}
178 
179 	virtual void OnRepositoryDone(uint32 headerSize, uint32 repositoryInfoSize,
180 		uint32 licenseCount, uint32 packageCount, uint32 packageAttributesSize,
181 		uint64 totalSize)
182 	{
183 		if (fQuiet || !fVerbose)
184 			return;
185 
186 		printf("----- Package Repository Info -----\n");
187 		if (fVerbose)
188 			printf("embedded license count   %10" B_PRIu32 "\n", licenseCount);
189 		printf("package count            %10" B_PRIu32 "\n", packageCount);
190 		printf("-----------------------------------\n");
191 		printf("header size:             %10" B_PRIu32 "\n", headerSize);
192 		printf("repository header size:  %10" B_PRIu32 "\n",
193 			repositoryInfoSize);
194 		printf("package attributes size: %10" B_PRIu32 "\n",
195 			packageAttributesSize);
196 		printf("total size:              %10" B_PRIu64 "\n", totalSize);
197 		printf("-----------------------------------\n");
198 	}
199 
200 private:
201 	bool fVerbose;
202 	bool fQuiet;
203 };
204 
205 
206 }	// anonymous namespace
207 
208 
209 int
210 command_update(int argc, const char* const* argv)
211 {
212 	const char* changeToDirectory = NULL;
213 	bool quiet = false;
214 	bool verbose = false;
215 
216 	while (true) {
217 		static struct option sLongOptions[] = {
218 			{ "help", no_argument, 0, 'h' },
219 			{ "quiet", no_argument, 0, 'q' },
220 			{ "verbose", no_argument, 0, 'v' },
221 			{ 0, 0, 0, 0 }
222 		};
223 
224 		opterr = 0; // don't print errors
225 		int c = getopt_long(argc, (char**)argv, "+C:hqv", sLongOptions, NULL);
226 		if (c == -1)
227 			break;
228 
229 		switch (c) {
230 			case 'C':
231 				changeToDirectory = optarg;
232 				break;
233 
234 			case 'h':
235 				print_usage_and_exit(false);
236 				break;
237 
238 			case 'q':
239 				quiet = true;
240 				break;
241 
242 			case 'v':
243 				verbose = true;
244 				break;
245 
246 			default:
247 				print_usage_and_exit(true);
248 				break;
249 		}
250 	}
251 
252 	// The remaining three arguments are the source and target repository file
253 	// plus the package list file.
254 	if (optind + 3 != argc)
255 		print_usage_and_exit(true);
256 
257 	const char* sourceRepositoryFileName = argv[optind++];
258 	const char* targetRepositoryFileName = argv[optind++];
259 	const char* packageListFileName = argv[optind++];
260 
261 	BStandardErrorOutput errorOutput;
262 	RepositoryWriterListener listener(verbose, quiet);
263 
264 	BEntry sourceRepositoryEntry(sourceRepositoryFileName);
265 	if (!sourceRepositoryEntry.Exists()) {
266 		listener.PrintError(
267 			"Error: given source repository file '%s' doesn't exist!\n",
268 			sourceRepositoryFileName);
269 		return 1;
270 	}
271 	// determine path for 'repo.info' file from given new repository file
272 	BString repositoryInfoFileName(targetRepositoryFileName);
273 	repositoryInfoFileName.Append(".info");
274 	BEntry repositoryInfoEntry(repositoryInfoFileName.String());
275 	BRepositoryInfo repositoryInfo(repositoryInfoEntry);
276 	status_t result = repositoryInfo.InitCheck();
277 	if (result != B_OK) {
278 		listener.PrintError(
279 			"Error: can't parse/read repository-info file %s : %s\n",
280 			repositoryInfoFileName.String(), strerror(result));
281 		return 1;
282 	}
283 
284 	// open source repository
285 	BRepositoryReader repositoryReader(&errorOutput);
286 	result = repositoryReader.Init(sourceRepositoryFileName);
287 	if (result != B_OK) {
288 		listener.PrintError(
289 			"Error: can't read from old repository file : %s\n",
290 			strerror(result));
291 		return 1;
292 	}
293 
294 	// collect package infos from source repository
295 	PackageInfos packageInfos;
296 	PackageInfosCollector packageInfosCollector(packageInfos, &errorOutput);
297 	result = repositoryReader.ParseContent(&packageInfosCollector);
298 	if (result != B_OK) {
299 		listener.PrintError(
300 			"Error: couldn't fetch package infos from old repository : %s\n",
301 			strerror(result));
302 		return 1;
303 	}
304 
305 	// create new repository
306 	BRepositoryWriter repositoryWriter(&listener, &repositoryInfo);
307 	BString tempRepositoryFileName(targetRepositoryFileName);
308 	tempRepositoryFileName += ".___new___";
309 	if ((result = repositoryWriter.Init(tempRepositoryFileName.String()))
310 			!= B_OK) {
311 		listener.PrintError("Error: can't initialize repository-writer : %s\n",
312 			strerror(result));
313 		return 1;
314 	}
315 
316 	BEntry tempRepositoryFile(tempRepositoryFileName.String());
317 	BPath targetRepositoryFilePath(targetRepositoryFileName);
318 
319 	BObjectList<BString> packageNames(100, true);
320 	if ((result = parsePackageListFile(packageListFileName, &packageNames))
321 			!= B_OK) {
322 		listener.PrintError(
323 			"Error: Failed to read package-list-file \"%s\": %s\n",
324 			packageListFileName, strerror(result));
325 		return 1;
326 	}
327 
328 	// change directory, if requested
329 	if (changeToDirectory != NULL) {
330 		if (chdir(changeToDirectory) != 0) {
331 			listener.PrintError(
332 				"Error: Failed to change the current working directory to "
333 				"\"%s\": %s\n", changeToDirectory, strerror(errno));
334 			return 1;
335 		}
336 	}
337 
338 	// add all given package files
339 	for (int i = 0; i < packageNames.CountItems(); ++i) {
340 		BPackageInfo packageInfo;
341 		if ((result = packageInfo.ReadFromPackageFile(
342 					packageNames.ItemAt(i)->String())) != B_OK) {
343 			listener.PrintError(
344 				"Error: Failed to read package-info from \"%s\": %s\n",
345 				packageNames.ItemAt(i)->String(), strerror(result));
346 			return 1;
347 		}
348 		PackageInfos::iterator infoIter = packageInfos.find(packageInfo);
349 		if (infoIter != packageInfos.end()) {
350 			infoIter->second = true;
351 			if ((result = repositoryWriter.AddPackageInfo(infoIter->first))
352 					!= B_OK)
353 				return 1;
354 			if (verbose) {
355 				printf("keeping '%s-%s'\n", infoIter->first.Name().String(),
356 					infoIter->first.Version().ToString().String());
357 			}
358 		} else {
359 			BEntry entry(packageNames.ItemAt(i)->String());
360 			if ((result = repositoryWriter.AddPackage(entry)) != B_OK)
361 				return 1;
362 			if (!quiet) {
363 				printf("added '%s' ...\n",
364 					packageNames.ItemAt(i)->String());
365 			}
366 		}
367 	}
368 
369 	// tell about packages dropped from repository
370 	PackageInfos::const_iterator infoIter;
371 	for (infoIter = packageInfos.begin(); infoIter != packageInfos.end();
372 			++infoIter) {
373 		if (!infoIter->second) {
374 			printf("dropped '%s-%s'\n", infoIter->first.Name().String(),
375 				infoIter->first.Version().ToString().String());
376 		}
377 	}
378 
379 
380 	// write the repository
381 	result = repositoryWriter.Finish();
382 	if (result != B_OK)
383 		return 1;
384 
385 	result = tempRepositoryFile.Rename(targetRepositoryFilePath.Leaf(), true);
386 	if (result != B_OK) {
387 		printf("Error: unable to rename repository %s to %s - %s\n",
388 			tempRepositoryFileName.String(), targetRepositoryFileName,
389 			strerror(result));
390 		return 1;
391 	}
392 
393 	if (verbose) {
394 		printf("\nsuccessfully created repository '%s'\n",
395 			targetRepositoryFileName);
396 	}
397 
398 	return 0;
399 }
400