xref: /haiku/src/apps/haikudepot/server/PopulatePkgUserRatingsFromServerProcess.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
1 /*
2  * Copyright 2016-2024, Andrew Lindesay <apl@lindesay.co.nz>.
3  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  *
6  * Note that this file included code earlier from `Model.cpp` and
7  * copyrights have been latterly been carried across in 2024.
8  */
9 
10 
11 #include "PopulatePkgUserRatingsFromServerProcess.h"
12 
13 #include <Autolock.h>
14 #include <Catalog.h>
15 
16 #include "Logger.h"
17 #include "ServerHelper.h"
18 
19 
20 #undef B_TRANSLATION_CONTEXT
21 #define B_TRANSLATION_CONTEXT "PopulatePkgUserRatingsFromServerProcess"
22 
23 
24 PopulatePkgUserRatingsFromServerProcess::PopulatePkgUserRatingsFromServerProcess(
25 	PackageInfoRef packageInfo, Model* model)
26 	:
27 	fModel(model),
28 	fPackageInfo(packageInfo)
29 {
30 }
31 
32 
33 PopulatePkgUserRatingsFromServerProcess::~PopulatePkgUserRatingsFromServerProcess()
34 {
35 }
36 
37 
38 const char*
39 PopulatePkgUserRatingsFromServerProcess::Name() const
40 {
41 	return "PopulatePkgUserRatingsFromServerProcess";
42 }
43 
44 
45 const char*
46 PopulatePkgUserRatingsFromServerProcess::Description() const
47 {
48 	return B_TRANSLATE("Fetching user ratings for package");
49 }
50 
51 
52 status_t
53 PopulatePkgUserRatingsFromServerProcess::RunInternal()
54 {
55 	// TODO; use API spec to code generation techniques instead of this manually written client.
56 
57 	status_t status = B_OK;
58 
59 	// Retrieve info from web-app
60 	BMessage info;
61 
62 	BString packageName;
63 	BString webAppRepositoryCode;
64 
65 	{
66 		BAutolock locker(&fLock);
67 		packageName = fPackageInfo->Name();
68 		const DepotInfo* depot = fModel->DepotForName(fPackageInfo->DepotName());
69 
70 		if (depot != NULL)
71 			webAppRepositoryCode = depot->WebAppRepositoryCode();
72 	}
73 
74 	if (status == B_OK) {
75 		status = fModel->GetWebAppInterface()->RetrieveUserRatingsForPackageForDisplay(packageName,
76 			webAppRepositoryCode, BString(), 0, PACKAGE_INFO_MAX_USER_RATINGS, info);
77 			// ^ note intentionally not using the repository source code as this would then show
78 			// too few results as it would be architecture specific.
79 	}
80 
81 	UserRatingInfoRef userRatingInfo(new UserRatingInfo(), true);
82 
83 	if (status == B_OK) {
84 		// Parse message
85 		BMessage result;
86 		BMessage items;
87 
88 		status = info.FindMessage("result", &result);
89 
90 		if (status == B_OK) {
91 
92 			if (result.FindMessage("items", &items) == B_OK) {
93 
94 				int32 index = 0;
95 				while (true) {
96 					BString name;
97 					name << index++;
98 
99 					BMessage item;
100 					if (items.FindMessage(name, &item) != B_OK)
101 						break;
102 
103 					BString code;
104 					if (item.FindString("code", &code) != B_OK) {
105 						HDERROR("corrupt user rating at index %" B_PRIi32, index);
106 						continue;
107 					}
108 
109 					BString user;
110 					BMessage userInfo;
111 					if (item.FindMessage("user", &userInfo) != B_OK
112 							|| userInfo.FindString("nickname", &user) != B_OK) {
113 						HDERROR("ignored user rating [%s] without a user nickname", code.String());
114 						continue;
115 					}
116 
117 					// Extract basic info, all items are optional
118 					BString languageCode;
119 					BString comment;
120 					double rating;
121 					item.FindString("naturalLanguageCode", &languageCode);
122 					item.FindString("comment", &comment);
123 					if (item.FindDouble("rating", &rating) != B_OK)
124 						rating = -1;
125 					if (comment.Length() == 0 && rating == -1) {
126 						HDERROR("rating [%s] has no comment or rating so will"
127 								" be ignored",
128 							code.String());
129 						continue;
130 					}
131 
132 					// For which version of the package was the rating?
133 					BString major = "?";
134 					BString minor = "?";
135 					BString micro = "";
136 					double revision = -1;
137 					BString architectureCode = "";
138 					BMessage version;
139 					if (item.FindMessage("pkgVersion", &version) == B_OK) {
140 						version.FindString("major", &major);
141 						version.FindString("minor", &minor);
142 						version.FindString("micro", &micro);
143 						version.FindDouble("revision", &revision);
144 						version.FindString("architectureCode", &architectureCode);
145 					}
146 					BString versionString = major;
147 					versionString << ".";
148 					versionString << minor;
149 					if (!micro.IsEmpty()) {
150 						versionString << ".";
151 						versionString << micro;
152 					}
153 					if (revision > 0) {
154 						versionString << "-";
155 						versionString << (int)revision;
156 					}
157 
158 					if (!architectureCode.IsEmpty()) {
159 						versionString << " " << STR_MDASH << " ";
160 						versionString << architectureCode;
161 					}
162 
163 					double createTimestamp;
164 					item.FindDouble("createTimestamp", &createTimestamp);
165 
166 					// Add the rating to the PackageInfo
167 					UserRatingRef userRating(
168 						new UserRating(UserInfo(user), rating, comment, languageCode,
169 							// note that language identifiers are "code" in HDS and "id" in Haiku
170 							versionString, (uint64) createTimestamp),
171 						true);
172 					userRatingInfo->AddUserRating(userRating);
173 					HDDEBUG("rating [%s] retrieved from server", code.String());
174 				}
175 
176 				userRatingInfo->SetUserRatingsPopulated();
177 				HDDEBUG("did retrieve %" B_PRIi32 " user ratings for [%s]", index - 1,
178 					packageName.String());
179 			}
180 		} else {
181 			int32 errorCode = WebAppInterface::ErrorCodeFromResponse(info);
182 
183 			if (errorCode != ERROR_CODE_NONE)
184 				ServerHelper::NotifyServerJsonRpcError(info);
185 		}
186 	} else {
187 		ServerHelper::NotifyTransportError(status);
188 	}
189 
190 	// Now fetch the user rating summary which is derived separately as it is
191 	// not just based on the user-ratings downloaded; it is calculated according
192 	// to an algorithm. This is best executed server-side to avoid discrepancy.
193 
194 	BMessage summaryResponse;
195 
196 	if (status == B_OK) {
197 		status = fModel->GetWebAppInterface()->RetrieveUserRatingSummaryForPackage(packageName,
198 			webAppRepositoryCode, summaryResponse);
199 	}
200 
201 	if (status == B_OK) {
202 		// Parse message
203 
204 		UserRatingSummaryRef userRatingSummary(new UserRatingSummary(), true);
205 
206 		BMessage result;
207 
208 		// TODO; this entire BMessage handling is historical and needs to be swapped out with
209 		//	generated code from the API spec; it just takes time unfortunately.
210 
211 		status = summaryResponse.FindMessage("result", &result);
212 
213 		double sampleSizeF;
214 		bool hasData;
215 
216 		if (status == B_OK)
217 			status = result.FindDouble("sampleSize", &sampleSizeF);
218 
219 		if (status == B_OK)
220 			userRatingSummary->SetRatingCount(static_cast<int>(sampleSizeF));
221 
222 		hasData = status == B_OK && userRatingSummary->RatingCount() > 0;
223 
224 		if (hasData) {
225 			double ratingF;
226 
227 			if (status == B_OK)
228 				status = result.FindDouble("rating", &ratingF);
229 
230 			if (status == B_OK)
231 				userRatingSummary->SetAverageRating(ratingF);
232 		}
233 
234 		if (hasData) {
235 			BMessage ratingDistributionItems;
236 			BMessage item;
237 
238 			status = result.FindMessage("ratingDistribution", &ratingDistributionItems);
239 
240 			int32 index = 0;
241 			while (status == B_OK) {
242 				BString name;
243 				name << index++;
244 
245 				BMessage ratingDistributionItem;
246 				if (ratingDistributionItems.FindMessage(name, &ratingDistributionItem) != B_OK)
247 					break;
248 
249 				double ratingDistributionRatingF;
250 
251 				if (status == B_OK) {
252 					status
253 						= ratingDistributionItem.FindDouble("rating", &ratingDistributionRatingF);
254 				}
255 
256 				double ratingDistributionTotalF;
257 
258 				if (status == B_OK)
259 					status = ratingDistributionItem.FindDouble("total", &ratingDistributionTotalF);
260 
261 				userRatingSummary->SetRatingByStar(static_cast<int>(ratingDistributionRatingF),
262 					static_cast<int>(ratingDistributionTotalF));
263 			}
264 
265 			userRatingInfo->SetSummary(userRatingSummary);
266 		} else {
267 			int32 errorCode = WebAppInterface::ErrorCodeFromResponse(summaryResponse);
268 
269 			if (errorCode != ERROR_CODE_NONE)
270 				ServerHelper::NotifyServerJsonRpcError(summaryResponse);
271 		}
272 	} else {
273 		ServerHelper::NotifyTransportError(status);
274 	}
275 
276 	if (status == B_OK) {
277 		// TODO; later make the PackageInfo immutable to avoid the need for locking here.
278 		BAutolock locker(&fLock);
279 		fPackageInfo->SetUserRatingInfo(userRatingInfo);
280 	}
281 
282 	return status;
283 }
284