1 /* 2 * Copyright 2017-2019, 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 int32 IndexOfCategoryByName( 55 const BString& name) const; 56 int32 IndexOfCategoryByCode( 57 const BString& code) const; 58 59 BString fDepotName; 60 Model* fModel; 61 CategoryList fCategories; 62 Stoppable* fStoppable; 63 uint32 fCount; 64 bool fDebugEnabled; 65 }; 66 67 68 PackageFillingPkgListener::PackageFillingPkgListener(Model* model, 69 BString& depotName, Stoppable* stoppable) 70 : 71 fDepotName(depotName), 72 fModel(model), 73 fStoppable(stoppable), 74 fCount(0), 75 fDebugEnabled(Logger::IsDebugEnabled()) 76 { 77 fCategories = model->Categories(); 78 } 79 80 81 PackageFillingPkgListener::~PackageFillingPkgListener() 82 { 83 } 84 85 86 // TODO; performance could be improved by not needing the linear search 87 88 inline int32 89 PackageFillingPkgListener::IndexOfCategoryByName( 90 const BString& name) const 91 { 92 int32 i; 93 int32 categoryCount = fCategories.CountItems(); 94 95 for (i = 0; i < categoryCount; i++) { 96 const CategoryRef categoryRef = fCategories.ItemAtFast(i); 97 98 if (categoryRef->Name() == name) 99 return i; 100 } 101 102 return -1; 103 } 104 105 106 bool 107 PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package, 108 DumpExportPkg* pkg) 109 { 110 int32 i; 111 112 // Collects all of the changes here into one set of notifications to 113 // the package's listeners. This way the quantity of BMessages 114 // communicated back to listeners is considerably reduced. See stop 115 // invocation later in this method. 116 117 package->StartCollatingChanges(); 118 119 if (0 != pkg->CountPkgVersions()) { 120 121 // this makes the assumption that the only version will be the 122 // latest one. 123 124 DumpExportPkgVersion* pkgVersion = pkg->PkgVersionsItemAt(0); 125 126 if (!pkgVersion->TitleIsNull()) 127 package->SetTitle(*(pkgVersion->Title())); 128 129 if (!pkgVersion->SummaryIsNull()) 130 package->SetShortDescription(*(pkgVersion->Summary())); 131 132 if (!pkgVersion->DescriptionIsNull()) 133 package->SetFullDescription(*(pkgVersion->Description())); 134 135 if (!pkgVersion->PayloadLengthIsNull()) 136 package->SetSize(pkgVersion->PayloadLength()); 137 } 138 139 int32 countPkgCategories = pkg->CountPkgCategories(); 140 141 for (i = 0; i < countPkgCategories; i++) { 142 BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code(); 143 int categoryIndex = IndexOfCategoryByName(*(categoryCode)); 144 145 if (categoryIndex == -1) { 146 printf("unable to find the category for [%s]\n", 147 categoryCode->String()); 148 } else { 149 package->AddCategory( 150 fCategories.ItemAtFast(categoryIndex)); 151 } 152 } 153 154 RatingSummary summary; 155 summary.averageRating = RATING_MISSING; 156 157 if (!pkg->DerivedRatingIsNull()) 158 summary.averageRating = pkg->DerivedRating(); 159 160 package->SetRatingSummary(summary); 161 162 if (!pkg->ProminenceOrderingIsNull()) 163 package->SetProminence(pkg->ProminenceOrdering()); 164 165 int32 countPkgScreenshots = pkg->CountPkgScreenshots(); 166 167 for (i = 0; i < countPkgScreenshots; i++) { 168 DumpExportPkgScreenshot* screenshot = pkg->PkgScreenshotsItemAt(i); 169 package->AddScreenshotInfo(ScreenshotInfo( 170 *(screenshot->Code()), 171 static_cast<int32>(screenshot->Width()), 172 static_cast<int32>(screenshot->Height()), 173 static_cast<int32>(screenshot->Length()) 174 )); 175 } 176 177 if (fDebugEnabled) { 178 printf("did populate data for [%s] (%s)\n", pkg->Name()->String(), 179 fDepotName.String()); 180 } 181 182 fCount++; 183 184 package->EndCollatingChanges(); 185 186 return !fStoppable->WasStopped(); 187 } 188 189 190 uint32 191 PackageFillingPkgListener::Count() 192 { 193 return fCount; 194 } 195 196 197 bool 198 PackageFillingPkgListener::Handle(DumpExportPkg* pkg) 199 { 200 const DepotInfo* depotInfo = fModel->DepotForName(fDepotName); 201 202 if (depotInfo != NULL) { 203 BString packageName = *(pkg->Name()); 204 int32 packageIndex = depotInfo->PackageIndexByName(packageName); 205 206 if (-1 != packageIndex) { 207 PackageList packages = depotInfo->Packages(); 208 const PackageInfoRef& packageInfoRef = 209 packages.ItemAtFast(packageIndex); 210 211 AutoLocker<BLocker> locker(fModel->Lock()); 212 ConsumePackage(packageInfoRef, pkg); 213 } else { 214 printf("[PackageFillingPkgListener] unable to find the pkg [%s]\n", 215 packageName.String()); 216 } 217 } else { 218 printf("[PackageFillingPkgListener] unable to find the depot [%s]\n", 219 fDepotName.String()); 220 } 221 222 return !fStoppable->WasStopped(); 223 } 224 225 226 void 227 PackageFillingPkgListener::Complete() 228 { 229 } 230 231 232 ServerPkgDataUpdateProcess::ServerPkgDataUpdateProcess( 233 BString naturalLanguageCode, 234 BString depotName, 235 Model *model, 236 uint32 serverProcessOptions) 237 : 238 AbstractSingleFileServerProcess(serverProcessOptions), 239 fNaturalLanguageCode(naturalLanguageCode), 240 fModel(model), 241 fDepotName(depotName) 242 { 243 fName.SetToFormat("ServerPkgDataUpdateProcess<%s>", depotName.String()); 244 fDescription.SetTo( 245 B_TRANSLATE("Synchronizing package data for repository " 246 "'%REPO_NAME%'")); 247 fDescription.ReplaceAll("%REPO_NAME%", depotName.String()); 248 } 249 250 251 ServerPkgDataUpdateProcess::~ServerPkgDataUpdateProcess() 252 { 253 } 254 255 256 const char* 257 ServerPkgDataUpdateProcess::Name() const 258 { 259 return fName.String(); 260 } 261 262 263 const char* 264 ServerPkgDataUpdateProcess::Description() const 265 { 266 return fDescription.String(); 267 } 268 269 270 BString 271 ServerPkgDataUpdateProcess::UrlPathComponent() 272 { 273 BString urlPath; 274 urlPath.SetToFormat("/__pkg/all-%s-%s.json.gz", 275 _DeriveWebAppRepositorySourceCode().String(), 276 fNaturalLanguageCode.String()); 277 return urlPath; 278 } 279 280 281 status_t 282 ServerPkgDataUpdateProcess::GetLocalPath(BPath& path) const 283 { 284 BString webAppRepositorySourceCode = _DeriveWebAppRepositorySourceCode(); 285 286 if (!webAppRepositorySourceCode.IsEmpty()) { 287 return fModel->DumpExportPkgDataPath(path, webAppRepositorySourceCode); 288 } 289 290 return B_ERROR; 291 } 292 293 294 status_t 295 ServerPkgDataUpdateProcess::ProcessLocalData() 296 { 297 BStopWatch watch("ServerPkgDataUpdateProcess::ProcessLocalData", true); 298 299 PackageFillingPkgListener* itemListener = 300 new PackageFillingPkgListener(fModel, fDepotName, this); 301 ObjectDeleter<PackageFillingPkgListener> 302 itemListenerDeleter(itemListener); 303 304 BulkContainerDumpExportPkgJsonListener* listener = 305 new BulkContainerDumpExportPkgJsonListener(itemListener); 306 ObjectDeleter<BulkContainerDumpExportPkgJsonListener> 307 listenerDeleter(listener); 308 309 BPath localPath; 310 status_t result = GetLocalPath(localPath); 311 312 if (result != B_OK) 313 return result; 314 315 result = ParseJsonFromFileWithListener(listener, localPath); 316 317 if (B_OK != result) 318 return result; 319 320 if (Logger::IsInfoEnabled()) { 321 double secs = watch.ElapsedTime() / 1000000.0; 322 printf("[%s] did process %" B_PRIi32 " packages' data " 323 "in (%6.3g secs)\n", Name(), itemListener->Count(), secs); 324 } 325 326 return listener->ErrorStatus(); 327 } 328 329 330 status_t 331 ServerPkgDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const 332 { 333 return GetLocalPath(path); 334 } 335 336 337 void 338 ServerPkgDataUpdateProcess::GetStandardMetaDataJsonPath( 339 BString& jsonPath) const 340 { 341 jsonPath.SetTo("$.info"); 342 } 343 344 345 BString 346 ServerPkgDataUpdateProcess::_DeriveWebAppRepositorySourceCode() const 347 { 348 const DepotInfo* depot = fModel->DepotForName(fDepotName); 349 350 if (depot == NULL) { 351 return BString(); 352 } 353 354 return depot->WebAppRepositorySourceCode(); 355 } 356 357 358 status_t 359 ServerPkgDataUpdateProcess::RunInternal() 360 { 361 if (_DeriveWebAppRepositorySourceCode().IsEmpty()) { 362 if (Logger::IsInfoEnabled()) { 363 printf("[%s] am not updating data for depot [%s] as there is no" 364 " web app repository source code available\n", 365 Name(), fDepotName.String()); 366 } 367 return B_OK; 368 } 369 370 return AbstractSingleFileServerProcess::RunInternal(); 371 } 372