xref: /haiku/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp (revision 9f3bdf3d039430b5172c424def20ce5d9f7367d4)
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 
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(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 
168 uint32
169 PackageFillingPkgListener::Count()
170 {
171 	return fCount;
172 }
173 
174 
175 bool
176 PackageFillingPkgListener::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 
199 void
200 PackageFillingPkgListener::Complete()
201 {
202 }
203 
204 
205 ServerPkgDataUpdateProcess::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 
222 ServerPkgDataUpdateProcess::~ServerPkgDataUpdateProcess()
223 {
224 }
225 
226 
227 const char*
228 ServerPkgDataUpdateProcess::Name() const
229 {
230 	return fName.String();
231 }
232 
233 
234 const char*
235 ServerPkgDataUpdateProcess::Description() const
236 {
237 	return fDescription.String();
238 }
239 
240 
241 BString
242 ServerPkgDataUpdateProcess::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 
252 status_t
253 ServerPkgDataUpdateProcess::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 
266 status_t
267 ServerPkgDataUpdateProcess::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 
302 status_t
303 ServerPkgDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const
304 {
305 	return GetLocalPath(path);
306 }
307 
308 
309 void
310 ServerPkgDataUpdateProcess::GetStandardMetaDataJsonPath(
311 	BString& jsonPath) const
312 {
313 	jsonPath.SetTo("$.info");
314 }
315 
316 
317 BString
318 ServerPkgDataUpdateProcess::_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 
330 status_t
331 ServerPkgDataUpdateProcess::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