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