xref: /haiku/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp (revision 372b901dfeada686207d00bbcce456f748bbda12)
1 /*
2  * Copyright 2017-2021, 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 <support/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 
39 class PackageFillingPkgListener : public DumpExportPkgListener {
40 public:
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 
52 private:
53 			int32				IndexOfPackageByName(const BString& name) const;
54 
55 private:
56 			BString				fDepotName;
57 			Model*				fModel;
58 			std::vector<CategoryRef>
59 								fCategories;
60 			Stoppable*			fStoppable;
61 			uint32				fCount;
62 			bool				fDebugEnabled;
63 };
64 
65 
66 PackageFillingPkgListener::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 
78 PackageFillingPkgListener::~PackageFillingPkgListener()
79 {
80 }
81 
82 
83 bool
84 PackageFillingPkgListener::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(pkgVersion->PayloadLength());
114 	}
115 
116 	int32 countPkgCategories = pkg->CountPkgCategories();
117 
118 	for (i = 0; i < countPkgCategories; i++) {
119 		BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code();
120 		CategoryRef category = fModel->CategoryByCode(*categoryCode);
121 
122 		if (!category.IsSet()) {
123 			HDERROR("unable to find the category for [%s]",
124 				categoryCode->String());
125 		} else
126 			package->AddCategory(category);
127 	}
128 
129 	RatingSummary summary;
130 	summary.averageRating = RATING_MISSING;
131 
132 	if (!pkg->DerivedRatingIsNull())
133 		summary.averageRating = pkg->DerivedRating();
134 
135 	package->SetRatingSummary(summary);
136 
137 	package->SetHasChangelog(pkg->HasChangelog());
138 
139 	if (!pkg->ProminenceOrderingIsNull())
140 		package->SetProminence(pkg->ProminenceOrdering());
141 
142 	int32 countPkgScreenshots = pkg->CountPkgScreenshots();
143 
144 	for (i = 0; i < countPkgScreenshots; i++) {
145 		DumpExportPkgScreenshot* screenshot = pkg->PkgScreenshotsItemAt(i);
146 		package->AddScreenshotInfo(ScreenshotInfoRef(new ScreenshotInfo(
147 			*(screenshot->Code()),
148 			static_cast<int32>(screenshot->Width()),
149 			static_cast<int32>(screenshot->Height()),
150 			static_cast<int32>(screenshot->Length())
151 		), true));
152 	}
153 
154 	HDDEBUG("did populate data for [%s] (%s)", pkg->Name()->String(),
155 			fDepotName.String());
156 
157 	fCount++;
158 
159 	package->EndCollatingChanges();
160 
161 	return !fStoppable->WasStopped();
162 }
163 
164 
165 uint32
166 PackageFillingPkgListener::Count()
167 {
168 	return fCount;
169 }
170 
171 
172 bool
173 PackageFillingPkgListener::Handle(DumpExportPkg* pkg)
174 {
175 	AutoLocker<BLocker> locker(fModel->Lock());
176 	DepotInfoRef depot = fModel->DepotForName(fDepotName);
177 
178 	if (depot.Get() != NULL) {
179 		const BString packageName = *(pkg->Name());
180 		PackageInfoRef package = depot->PackageByName(packageName);
181 		if (package.Get() != NULL)
182 			ConsumePackage(package, pkg);
183 		else {
184 			HDINFO("[PackageFillingPkgListener] unable to find the pkg [%s]",
185 				packageName.String());
186 		}
187 	} else {
188 		HDINFO("[PackageFillingPkgListener] unable to find the depot [%s]",
189 			fDepotName.String());
190 	}
191 
192 	return !fStoppable->WasStopped();
193 }
194 
195 
196 void
197 PackageFillingPkgListener::Complete()
198 {
199 }
200 
201 
202 ServerPkgDataUpdateProcess::ServerPkgDataUpdateProcess(
203 	BString naturalLanguageCode,
204 	BString depotName,
205 	Model *model,
206 	uint32 serverProcessOptions)
207 	:
208 	AbstractSingleFileServerProcess(serverProcessOptions),
209 	fNaturalLanguageCode(naturalLanguageCode),
210 	fModel(model),
211 	fDepotName(depotName)
212 {
213 	fName.SetToFormat("ServerPkgDataUpdateProcess<%s>", depotName.String());
214 	fDescription.SetTo(
215 		B_TRANSLATE("Synchronizing package data for repository "
216 			"'%REPO_NAME%'"));
217 	fDescription.ReplaceAll("%REPO_NAME%", depotName.String());
218 }
219 
220 
221 ServerPkgDataUpdateProcess::~ServerPkgDataUpdateProcess()
222 {
223 }
224 
225 
226 const char*
227 ServerPkgDataUpdateProcess::Name() const
228 {
229 	return fName.String();
230 }
231 
232 
233 const char*
234 ServerPkgDataUpdateProcess::Description() const
235 {
236 	return fDescription.String();
237 }
238 
239 
240 BString
241 ServerPkgDataUpdateProcess::UrlPathComponent()
242 {
243 	BString urlPath;
244 	urlPath.SetToFormat("/__pkg/all-%s-%s.json.gz",
245 		_DeriveWebAppRepositorySourceCode().String(),
246 		fNaturalLanguageCode.String());
247 	return urlPath;
248 }
249 
250 
251 status_t
252 ServerPkgDataUpdateProcess::GetLocalPath(BPath& path) const
253 {
254 	BString webAppRepositorySourceCode = _DeriveWebAppRepositorySourceCode();
255 
256 	if (!webAppRepositorySourceCode.IsEmpty()) {
257 		AutoLocker<BLocker> locker(fModel->Lock());
258 		return fModel->DumpExportPkgDataPath(path, webAppRepositorySourceCode);
259 	}
260 
261 	return B_ERROR;
262 }
263 
264 
265 status_t
266 ServerPkgDataUpdateProcess::ProcessLocalData()
267 {
268 	BStopWatch watch("ServerPkgDataUpdateProcess::ProcessLocalData", true);
269 
270 	PackageFillingPkgListener* itemListener =
271 		new PackageFillingPkgListener(fModel, fDepotName, this);
272 	ObjectDeleter<PackageFillingPkgListener>
273 		itemListenerDeleter(itemListener);
274 
275 	BulkContainerDumpExportPkgJsonListener* listener =
276 		new BulkContainerDumpExportPkgJsonListener(itemListener);
277 	ObjectDeleter<BulkContainerDumpExportPkgJsonListener>
278 		listenerDeleter(listener);
279 
280 	BPath localPath;
281 	status_t result = GetLocalPath(localPath);
282 
283 	if (result != B_OK)
284 		return result;
285 
286 	result = ParseJsonFromFileWithListener(listener, localPath);
287 
288 	if (B_OK != result)
289 		return result;
290 
291 	if (Logger::IsInfoEnabled()) {
292 		double secs = watch.ElapsedTime() / 1000000.0;
293 		HDINFO("[%s] did process %" B_PRIi32 " packages' data "
294 			"in  (%6.3g secs)", Name(), itemListener->Count(), secs);
295 	}
296 
297 	return listener->ErrorStatus();
298 }
299 
300 
301 status_t
302 ServerPkgDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const
303 {
304 	return GetLocalPath(path);
305 }
306 
307 
308 void
309 ServerPkgDataUpdateProcess::GetStandardMetaDataJsonPath(
310 	BString& jsonPath) const
311 {
312 	jsonPath.SetTo("$.info");
313 }
314 
315 
316 BString
317 ServerPkgDataUpdateProcess::_DeriveWebAppRepositorySourceCode() const
318 {
319 	const DepotInfo* depot = fModel->DepotForName(fDepotName);
320 
321 	if (depot == NULL) {
322 		return BString();
323 	}
324 
325 	return depot->WebAppRepositorySourceCode();
326 }
327 
328 
329 status_t
330 ServerPkgDataUpdateProcess::RunInternal()
331 {
332 	if (_DeriveWebAppRepositorySourceCode().IsEmpty()) {
333 		HDINFO("[%s] am not updating data for depot [%s] as there is no"
334 			" web app repository source code available",
335 			Name(), fDepotName.String());
336 		return B_OK;
337 	}
338 
339 	return AbstractSingleFileServerProcess::RunInternal();
340 }
341