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
34using BPackageKit::BHPKG::BRepositoryWriterListener;
35using BPackageKit::BHPKG::BRepositoryWriter;
36using namespace BPackageKit::BHPKG;
37using namespace BPackageKit;
38
39
40bool 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
48namespace
49{
50
51
52typedef std::map<BPackageInfo, bool> PackageInfos;
53
54
55status_t
56parsePackageListFile(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
81struct 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
132private:
133	PackageInfos& fPackageInfos;
134	BHPKG::BErrorOutput* fErrorOutput;
135	BRepositoryInfo fRepositoryInfo;
136	BPackageInfo fPackageInfo;
137	BPackageInfoContentHandler fPackageInfoContentHandler;
138};
139
140
141class RepositoryWriterListener	: public BRepositoryWriterListener {
142public:
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
200private:
201	bool fVerbose;
202	bool fQuiet;
203};
204
205
206}	// anonymous namespace
207
208
209int
210command_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