1/*
2 * Copyright 2017-2024, Andrew Lindesay <apl@lindesay.co.nz>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7#include "ServerPkgDataUpdateProcess.h"
8
9#include <stdio.h>
10#include <sys/stat.h>
11#include <time.h>
12
13#include <AutoDeleter.h>
14#include <AutoLocker.h>
15#include <Catalog.h>
16#include <FileIO.h>
17#include <StopWatch.h>
18#include <Url.h>
19
20#include "Logger.h"
21#include "ServerSettings.h"
22#include "StorageUtils.h"
23#include "DumpExportPkg.h"
24#include "DumpExportPkgCategory.h"
25#include "DumpExportPkgJsonListener.h"
26#include "DumpExportPkgScreenshot.h"
27#include "DumpExportPkgVersion.h"
28#include "HaikuDepotConstants.h"
29
30
31#undef B_TRANSLATION_CONTEXT
32#define B_TRANSLATION_CONTEXT "ServerPkgDataUpdateProcess"
33
34
35/*! This package listener (not at the JSON level) is feeding in the
36    packages as they are parsed and processing them.
37*/
38
39class PackageFillingPkgListener : public DumpExportPkgListener {
40public:
41								PackageFillingPkgListener(Model *model,
42									BString& depotName, Stoppable* stoppable);
43	virtual						~PackageFillingPkgListener();
44
45	virtual bool				ConsumePackage(const PackageInfoRef& package,
46									DumpExportPkg* pkg);
47	virtual	bool				Handle(DumpExportPkg* item);
48	virtual	void				Complete();
49
50			uint32				Count();
51
52private:
53			int32				IndexOfPackageByName(const BString& name) const;
54
55private:
56			BString				fDepotName;
57			Model*				fModel;
58			std::vector<CategoryRef>
59								fCategories;
60			Stoppable*			fStoppable;
61			uint32				fCount;
62			bool				fDebugEnabled;
63};
64
65
66PackageFillingPkgListener::PackageFillingPkgListener(Model* model,
67	BString& depotName, Stoppable* stoppable)
68	:
69	fDepotName(depotName),
70	fModel(model),
71	fStoppable(stoppable),
72	fCount(0),
73	fDebugEnabled(Logger::IsDebugEnabled())
74{
75}
76
77
78PackageFillingPkgListener::~PackageFillingPkgListener()
79{
80}
81
82
83bool
84PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package,
85	DumpExportPkg* pkg)
86{
87	int32 i;
88
89		// Collects all of the changes here into one set of notifications to
90		// the package's listeners.  This way the quantity of BMessages
91		// communicated back to listeners is considerably reduced.  See stop
92		// invocation later in this method.
93
94	package->StartCollatingChanges();
95
96	if (0 != pkg->CountPkgVersions()) {
97
98			// this makes the assumption that the only version will be the
99			// latest one.
100
101		DumpExportPkgVersion* pkgVersion = pkg->PkgVersionsItemAt(0);
102
103		if (!pkgVersion->TitleIsNull())
104			package->SetTitle(*(pkgVersion->Title()));
105
106		if (!pkgVersion->SummaryIsNull())
107			package->SetShortDescription(*(pkgVersion->Summary()));
108
109		if (!pkgVersion->DescriptionIsNull())
110			package->SetFullDescription(*(pkgVersion->Description()));
111
112		if (!pkgVersion->PayloadLengthIsNull())
113			package->SetSize(static_cast<off_t>(pkgVersion->PayloadLength()));
114
115		if (!pkgVersion->CreateTimestampIsNull())
116			package->SetVersionCreateTimestamp(pkgVersion->CreateTimestamp());
117	}
118
119	int32 countPkgCategories = pkg->CountPkgCategories();
120
121	for (i = 0; i < countPkgCategories; i++) {
122		BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code();
123		CategoryRef category = fModel->CategoryByCode(*categoryCode);
124
125		if (!category.IsSet()) {
126			HDERROR("unable to find the category for [%s]",
127				categoryCode->String());
128		} else
129			package->AddCategory(category);
130	}
131
132	RatingSummary summary;
133	summary.averageRating = RATING_MISSING;
134
135	if (!pkg->DerivedRatingIsNull())
136		summary.averageRating = pkg->DerivedRating();
137
138	package->SetRatingSummary(summary);
139
140	package->SetHasChangelog(pkg->HasChangelog());
141
142	if (!pkg->ProminenceOrderingIsNull())
143		package->SetProminence(pkg->ProminenceOrdering());
144
145	int32 countPkgScreenshots = pkg->CountPkgScreenshots();
146
147	for (i = 0; i < countPkgScreenshots; i++) {
148		DumpExportPkgScreenshot* screenshot = pkg->PkgScreenshotsItemAt(i);
149		package->AddScreenshotInfo(ScreenshotInfoRef(new ScreenshotInfo(
150			*(screenshot->Code()),
151			static_cast<int32>(screenshot->Width()),
152			static_cast<int32>(screenshot->Height()),
153			static_cast<int32>(screenshot->Length())
154		), true));
155	}
156
157	HDTRACE("did populate data for [%s] (%s)", pkg->Name()->String(),
158			fDepotName.String());
159
160	fCount++;
161
162	package->EndCollatingChanges();
163
164	return !fStoppable->WasStopped();
165}
166
167
168uint32
169PackageFillingPkgListener::Count()
170{
171	return fCount;
172}
173
174
175bool
176PackageFillingPkgListener::Handle(DumpExportPkg* pkg)
177{
178	AutoLocker<BLocker> locker(fModel->Lock());
179	DepotInfoRef depot = fModel->DepotForName(fDepotName);
180
181	if (depot.Get() != NULL) {
182		const BString packageName = *(pkg->Name());
183		PackageInfoRef package = depot->PackageByName(packageName);
184		if (package.Get() != NULL)
185			ConsumePackage(package, pkg);
186		else {
187			HDINFO("[PackageFillingPkgListener] unable to find the pkg [%s]",
188				packageName.String());
189		}
190	} else {
191		HDINFO("[PackageFillingPkgListener] unable to find the depot [%s]",
192			fDepotName.String());
193	}
194
195	return !fStoppable->WasStopped();
196}
197
198
199void
200PackageFillingPkgListener::Complete()
201{
202}
203
204
205ServerPkgDataUpdateProcess::ServerPkgDataUpdateProcess(
206	BString depotName,
207	Model *model,
208	uint32 serverProcessOptions)
209	:
210	AbstractSingleFileServerProcess(serverProcessOptions),
211	fModel(model),
212	fDepotName(depotName)
213{
214	fName.SetToFormat("ServerPkgDataUpdateProcess<%s>", depotName.String());
215	fDescription.SetTo(
216		B_TRANSLATE("Synchronizing package data for repository "
217			"'%REPO_NAME%'"));
218	fDescription.ReplaceAll("%REPO_NAME%", depotName.String());
219}
220
221
222ServerPkgDataUpdateProcess::~ServerPkgDataUpdateProcess()
223{
224}
225
226
227const char*
228ServerPkgDataUpdateProcess::Name() const
229{
230	return fName.String();
231}
232
233
234const char*
235ServerPkgDataUpdateProcess::Description() const
236{
237	return fDescription.String();
238}
239
240
241BString
242ServerPkgDataUpdateProcess::UrlPathComponent()
243{
244	BString urlPath;
245	urlPath.SetToFormat("/__pkg/all-%s-%s.json.gz",
246		_DeriveWebAppRepositorySourceCode().String(),
247		fModel->Language()->PreferredLanguage()->ID());
248	return urlPath;
249}
250
251
252status_t
253ServerPkgDataUpdateProcess::GetLocalPath(BPath& path) const
254{
255	BString webAppRepositorySourceCode = _DeriveWebAppRepositorySourceCode();
256
257	if (!webAppRepositorySourceCode.IsEmpty()) {
258		AutoLocker<BLocker> locker(fModel->Lock());
259		return fModel->DumpExportPkgDataPath(path, webAppRepositorySourceCode);
260	}
261
262	return B_ERROR;
263}
264
265
266status_t
267ServerPkgDataUpdateProcess::ProcessLocalData()
268{
269	BStopWatch watch("ServerPkgDataUpdateProcess::ProcessLocalData", true);
270
271	PackageFillingPkgListener* itemListener =
272		new PackageFillingPkgListener(fModel, fDepotName, this);
273	ObjectDeleter<PackageFillingPkgListener>
274		itemListenerDeleter(itemListener);
275
276	BulkContainerDumpExportPkgJsonListener* listener =
277		new BulkContainerDumpExportPkgJsonListener(itemListener);
278	ObjectDeleter<BulkContainerDumpExportPkgJsonListener>
279		listenerDeleter(listener);
280
281	BPath localPath;
282	status_t result = GetLocalPath(localPath);
283
284	if (result != B_OK)
285		return result;
286
287	result = ParseJsonFromFileWithListener(listener, localPath);
288
289	if (B_OK != result)
290		return result;
291
292	if (Logger::IsInfoEnabled()) {
293		double secs = watch.ElapsedTime() / 1000000.0;
294		HDINFO("[%s] did process %" B_PRIi32 " packages' data "
295			"in  (%6.3g secs)", Name(), itemListener->Count(), secs);
296	}
297
298	return listener->ErrorStatus();
299}
300
301
302status_t
303ServerPkgDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const
304{
305	return GetLocalPath(path);
306}
307
308
309void
310ServerPkgDataUpdateProcess::GetStandardMetaDataJsonPath(
311	BString& jsonPath) const
312{
313	jsonPath.SetTo("$.info");
314}
315
316
317BString
318ServerPkgDataUpdateProcess::_DeriveWebAppRepositorySourceCode() const
319{
320	const DepotInfo* depot = fModel->DepotForName(fDepotName);
321
322	if (depot == NULL) {
323		return BString();
324	}
325
326	return depot->WebAppRepositorySourceCode();
327}
328
329
330status_t
331ServerPkgDataUpdateProcess::RunInternal()
332{
333	if (_DeriveWebAppRepositorySourceCode().IsEmpty()) {
334		HDINFO("[%s] am not updating data for depot [%s] as there is no"
335			" web app repository source code available",
336			Name(), fDepotName.String());
337		return B_OK;
338	}
339
340	return AbstractSingleFileServerProcess::RunInternal();
341}
342