* Copyright 2016-2025, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*
* Note that this file included code earlier from `Model.cpp` and
* copyrights have been latterly been carried across in 2024.
*/
#include "PopulatePkgUserRatingsFromServerProcess.h"
#include <Autolock.h>
#include <Catalog.h>
#include "Logger.h"
#include "PackageUtils.h"
#include "ServerHelper.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PopulatePkgUserRatingsFromServerProcess"
PopulatePkgUserRatingsFromServerProcess::PopulatePkgUserRatingsFromServerProcess(
const BString& packageName, Model* model)
:
fModel(model),
fPackageName(packageName)
{
}
PopulatePkgUserRatingsFromServerProcess::~PopulatePkgUserRatingsFromServerProcess()
{
}
const char*
PopulatePkgUserRatingsFromServerProcess::Name() const
{
return "PopulatePkgUserRatingsFromServerProcess";
}
const char*
PopulatePkgUserRatingsFromServerProcess::Description() const
{
return B_TRANSLATE("Fetching user ratings for package");
}
status_t
PopulatePkgUserRatingsFromServerProcess::RunInternal()
{
if (!ServerHelper::IsNetworkAvailable()) {
HDINFO("no network so will not populate user ratings");
return B_OK;
}
status_t status = B_OK;
BMessage info;
BString webAppRepositoryCode = _WebAppRepositoryCode();
if (webAppRepositoryCode.IsEmpty()) {
HDERROR("unable to get the web app repository code for pkg [%s]", fPackageName.String());
status = B_ERROR;
}
if (status == B_OK) {
status = fModel->WebApp()->RetrieveUserRatingsForPackageForDisplay(fPackageName,
webAppRepositoryCode, BString(), 0, PACKAGE_INFO_MAX_USER_RATINGS, info);
}
PackageUserRatingInfoBuilder userRatingInfoBuilder;
if (status == B_OK) {
BMessage result;
BMessage items;
status = info.FindMessage("result", &result);
if (status == B_OK) {
if (result.FindMessage("items", &items) == B_OK) {
int32 index = 0;
while (true) {
BString name;
name << index++;
BMessage item;
if (items.FindMessage(name, &item) != B_OK)
break;
BString code;
if (item.FindString("code", &code) != B_OK) {
HDERROR("corrupt user rating at index %" B_PRIi32, index);
continue;
}
BString user;
BMessage userInfo;
if (item.FindMessage("user", &userInfo) != B_OK
|| userInfo.FindString("nickname", &user) != B_OK) {
HDERROR("ignored user rating [%s] without a user nickname", code.String());
continue;
}
BString languageCode;
BString comment;
double rating;
item.FindString("naturalLanguageCode", &languageCode);
item.FindString("comment", &comment);
if (item.FindDouble("rating", &rating) != B_OK)
rating = -1;
if (comment.Length() == 0 && rating == -1) {
HDERROR("rating [%s] has no comment or rating so will"
" be ignored",
code.String());
continue;
}
BString major = "?";
BString minor = "?";
BString micro = "";
double revision = -1;
BString architectureCode = "";
BMessage version;
if (item.FindMessage("pkgVersion", &version) == B_OK) {
version.FindString("major", &major);
version.FindString("minor", &minor);
version.FindString("micro", µ);
version.FindDouble("revision", &revision);
version.FindString("architectureCode", &architectureCode);
}
BString versionString = major;
versionString << ".";
versionString << minor;
if (!micro.IsEmpty()) {
versionString << ".";
versionString << micro;
}
if (revision > 0) {
versionString << "-";
versionString << (int)revision;
}
if (!architectureCode.IsEmpty()) {
versionString << " " << STR_MDASH << " ";
versionString << architectureCode;
}
double createTimestamp;
item.FindDouble("createTimestamp", &createTimestamp);
UserRatingRef userRating(
new UserRating(UserInfo(user), rating, comment, languageCode,
versionString, static_cast<uint64>(createTimestamp)),
true);
userRatingInfoBuilder.AddUserRating(userRating);
HDDEBUG("rating [%s] retrieved from server", code.String());
}
userRatingInfoBuilder.WithUserRatingsPopulated();
HDDEBUG("did retrieve %" B_PRIi32 " user ratings for [%s]", index - 1,
fPackageName.String());
}
} else {
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(info);
if (errorCode != ERROR_CODE_NONE)
ServerHelper::NotifyServerJsonRpcError(info);
}
} else {
ServerHelper::NotifyTransportError(status);
}
BMessage summaryResponse;
if (status == B_OK) {
status = fModel->WebApp()->RetrieveUserRatingSummaryForPackage(fPackageName,
webAppRepositoryCode, summaryResponse);
}
if (status == B_OK) {
UserRatingSummaryBuilder userRatingSummaryBuilder;
BMessage result;
status = summaryResponse.FindMessage("result", &result);
double sampleSizeF;
int sampleSize = 0;
bool hasData;
if (status == B_OK)
status = result.FindDouble("sampleSize", &sampleSizeF);
if (status == B_OK) {
sampleSize = static_cast<int>(sampleSizeF);
userRatingSummaryBuilder.WithRatingCount(sampleSize);
}
hasData = status == B_OK && sampleSize > 0;
if (hasData) {
double ratingF;
if (status == B_OK)
status = result.FindDouble("rating", &ratingF);
if (status == B_OK)
userRatingSummaryBuilder.WithAverageRating(ratingF);
}
if (hasData) {
BMessage ratingDistributionItems;
BMessage item;
status = result.FindMessage("ratingDistribution", &ratingDistributionItems);
int32 index = 0;
while (status == B_OK) {
BString name;
name << index++;
BMessage ratingDistributionItem;
if (ratingDistributionItems.FindMessage(name, &ratingDistributionItem) != B_OK)
break;
double ratingDistributionRatingF;
if (status == B_OK) {
status
= ratingDistributionItem.FindDouble("rating", &ratingDistributionRatingF);
}
double ratingDistributionTotalF;
if (status == B_OK)
status = ratingDistributionItem.FindDouble("total", &ratingDistributionTotalF);
userRatingSummaryBuilder.AddRatingByStar(
static_cast<int>(ratingDistributionRatingF),
static_cast<int>(ratingDistributionTotalF));
}
userRatingInfoBuilder.WithSummary(userRatingSummaryBuilder.BuildRef());
} else {
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(summaryResponse);
if (errorCode != ERROR_CODE_NONE)
ServerHelper::NotifyServerJsonRpcError(summaryResponse);
}
} else {
ServerHelper::NotifyTransportError(status);
}
if (status == B_OK) {
PackageInfoRef package = fModel->PackageForName(fPackageName);
PackageInfoRef updatedPackage = PackageInfoBuilder(package)
.WithUserRatingInfo(userRatingInfoBuilder.BuildRef())
.BuildRef();
fModel->AddPackage(updatedPackage);
}
return status;
}
const BString
PopulatePkgUserRatingsFromServerProcess::_WebAppRepositoryCode() const
{
const PackageInfoRef package = fModel->PackageForName(fPackageName);
const BString depotName = PackageUtils::DepotName(package);
const DepotInfoRef depot = fModel->DepotForName(depotName);
if (depot.IsSet())
return depot->WebAppRepositoryCode();
return BString();
}