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